import { asc, eq } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import type { MenuItemNode, MenuItemRow, MenuRow } from './model.js'
import { menuItems, menus, type MenusSchema } from './schema.js'

/**
 * Menu metadata plus nested item nodes returned by {@link getMenuTree}.
 *
 * @remarks Entity targets in `items` are unresolved and not visibility-filtered;
 * the caller MUST resolve each entity target through its own visibility/authorization
 * filter at render time and drop anything not publicly visible before end-user exposure —
 * see {@link getMenuTree}.
 */
export type MenuTree = {
  menu: MenuRow
  items: MenuItemNode[]
}

function rowToMenu(row: typeof menus.$inferSelect): MenuRow {
  return {
    id: row.id,
    key: row.key,
    label: row.label,
    createdAtMs: row.createdAtMs,
    updatedAtMs: row.updatedAtMs,
  }
}

function rowToItem(row: typeof menuItems.$inferSelect): MenuItemRow {
  return {
    id: row.id,
    menuId: row.menuId,
    parentId: row.parentId,
    position: row.position,
    depth: row.depth,
    label: row.label,
    targetKind: row.targetKind as 'url' | 'entity',
    url: row.url,
    targetEntityType: row.targetEntityType,
    targetEntityId: row.targetEntityId,
    openInNew: row.openInNew,
    createdAtMs: row.createdAtMs,
    updatedAtMs: row.updatedAtMs,
  }
}

function buildChildren(
  rows: MenuItemRow[],
  parentId: string,
  visited: Set<string>,
): MenuItemNode[] {
  const nodes: MenuItemNode[] = []
  for (const row of rows.filter((r) => r.parentId === parentId).sort((a, b) => a.position - b.position)) {
    if (visited.has(row.id)) continue
    visited.add(row.id)
    nodes.push({
      ...row,
      children: buildChildren(rows, row.id, visited),
    })
  }
  return nodes
}

function assembleTree(rows: MenuItemRow[]): MenuItemNode[] {
  const visited = new Set<string>()
  const itemIds = new Set(rows.map((r) => r.id))
  const roots = rows
    .filter((r) => r.parentId === null || !itemIds.has(r.parentId))
    .sort((a, b) => a.position - b.position)

  const tree: MenuItemNode[] = []
  for (const root of roots) {
    if (visited.has(root.id)) continue
    visited.add(root.id)
    tree.push({
      ...root,
      children: buildChildren(rows, root.id, visited),
    })
  }

  for (const row of rows.sort((a, b) => a.position - b.position)) {
    if (visited.has(row.id)) continue
    visited.add(row.id)
    tree.push({
      ...row,
      children: buildChildren(rows, row.id, visited),
    })
  }

  return tree.sort((a, b) => a.position - b.position)
}

export async function listMenus(db: Querier<MenusSchema>): Promise<MenuRow[]> {
  const rows = await db.select().from(menus).orderBy(asc(menus.key))
  return rows.map(rowToMenu)
}

/**
 * Load a menu and its item tree by stable key. Returns null when the key is missing.
 *
 * Returns UNRESOLVED opaque targets exactly as stored — entity targets are NOT
 * visibility-filtered; this core never resolves entity ids and cannot know whether
 * a target points at a draft, private, or trashed entity. The caller MUST resolve
 * each entity target through its own visibility/authorization filter at render time
 * and drop anything not publicly visible before exposing the tree to an end user.
 */
export async function getMenuTree(db: Querier<MenusSchema>, key: string): Promise<MenuTree | null> {
  const [menu] = await db.select().from(menus).where(eq(menus.key, key)).limit(1)
  if (!menu) return null
  const rows = await db
    .select()
    .from(menuItems)
    .where(eq(menuItems.menuId, menu.id))
    .orderBy(asc(menuItems.position))
  return {
    menu: rowToMenu(menu),
    items: assembleTree(rows.map(rowToItem)),
  }
}
