/**
 * cli:scaffold-core-seed — build-spec.ts
 *
 * Pure transformer: assembles a multi-application CoreSeedSpec from
 * already-fetched BA data. No DB / IPC access — the caller (the develop
 * pipeline handler `prepareCoreSeedContext`) loads:
 *   • menu tree   via baMenuService.listApplications/listModules/listSections/listResources
 *   • actors      via the BA actors service
 *   • permissions via the BA permissions service (or structured.permissions[] across PRDs)
 *
 * Then calls `buildCoreSeedSpec(...)` to produce the typed spec, drops it to a
 * temp JSON file, and the agent invokes `scaffold-core-seed --spec-file <path>`.
 *
 * Inputs are flat lists (modules across all apps, sections across all modules,
 * etc.). The transformer groups them by application via the parent-code chain
 * so the resulting spec has one `ApplicationSpec` per declared application.
 */
import type {
  ApplicationSpec,
  CoreSeedSpec,
  NavigationEntry,
  PermissionEntry,
  RoleEntry,
  RolePermission,
  TestUserEntry,
} from './types.js';

export interface MenuApplicationInput {
  code: string;
  label: string;
  icon?: string;
  iconType?: string;
  route?: string;
  displayOrder?: number;
  componentKey?: string;
}

export interface MenuModuleInput {
  code: string;
  label: string;
  applicationCode: string;
  icon?: string;
  iconType?: string;
  route?: string;
  displayOrder?: number;
  componentKey?: string;
}

export interface MenuSectionInput {
  code: string;
  label: string;
  moduleCode: string;
  icon?: string;
  iconType?: string;
  route?: string;
  displayOrder?: number;
  componentKey?: string;
}

export interface MenuResourceInput {
  code: string;
  label: string;
  sectionCode: string;
  icon?: string;
  iconType?: string;
  route?: string;
  displayOrder?: number;
  componentKey?: string;
}

export interface ActorInput {
  code: string;
  name: string;
  /** Navigation app code this actor's role is scoped to (e.g. "crm"). */
  applicationCode: string;
  isDefault?: boolean;
}

/**
 * BA permission row. `path` follows the 4-segment convention
 * `{app}.{module}.{section}.{action}`. `roles` is the list of actor codes
 * authorised for this permission — feeds the rolePermissions mapping.
 */
export interface PermissionInput {
  path: string;
  roles: string[];
}

export interface BuildCoreSeedSpecInput {
  /** Pascal-case namespace prefix (e.g. `TestV2`). Used for `src/{AppCode}.Infrastructure/...` paths. */
  appCode: string;
  projectPath: string;
  applications: MenuApplicationInput[];
  modules: MenuModuleInput[];
  sections: MenuSectionInput[];
  resources: MenuResourceInput[];
  actors: ActorInput[];
  permissions: PermissionInput[];
  /** Override the default dev test password. */
  testUserPassword?: string;
  testUserEmailDomain?: string;
  emitDevCredentialsManifest?: boolean;
}

const DEFAULT_ICON = 'Circle';
const DEFAULT_ICON_TYPE = 'Lucide';

export function buildCoreSeedSpec(input: BuildCoreSeedSpecInput): CoreSeedSpec {
  // Index parent relationships once so we can group sections/resources by app
  // without quadratic scans.
  const moduleToApp = new Map<string, string>();
  for (const m of input.modules) {
    moduleToApp.set(m.code, m.applicationCode);
  }
  const sectionToModule = new Map<string, string>();
  for (const s of input.sections) {
    sectionToModule.set(s.code, s.moduleCode);
  }
  const resourceToSection = new Map<string, string>();
  for (const r of input.resources) {
    resourceToSection.set(r.code, r.sectionCode);
  }

  const applications: ApplicationSpec[] = input.applications.map((appInput) =>
    buildApplicationSlice(appInput, input, { moduleToApp, sectionToModule, resourceToSection }),
  );

  return {
    appCode: input.appCode,
    applications,
    testUserPassword: input.testUserPassword ?? 'DevTest!2026Secure',
    testUserEmailDomain: input.testUserEmailDomain ?? 'smartstack.local',
    emitDevCredentialsManifest: input.emitDevCredentialsManifest ?? true,
    projectPath: input.projectPath,
  };
}

interface IndexMaps {
  moduleToApp: Map<string, string>;
  sectionToModule: Map<string, string>;
  resourceToSection: Map<string, string>;
}

function buildApplicationSlice(
  appInput: MenuApplicationInput,
  input: BuildCoreSeedSpecInput,
  idx: IndexMaps,
): ApplicationSpec {
  const appCode = appInput.code;

  const appModules = input.modules.filter((m) => m.applicationCode === appCode);
  const moduleCodesSet = new Set(appModules.map((m) => m.code));
  const moduleCodes = Array.from(moduleCodesSet);

  const appSections = input.sections.filter((s) => moduleCodesSet.has(s.moduleCode));
  const sectionCodesSet = new Set(appSections.map((s) => s.code));

  const appResources = input.resources.filter((r) => sectionCodesSet.has(r.sectionCode));

  const navigation: NavigationEntry[] = [
    toAppNavEntry(appInput),
    ...appModules.map(toModuleNavEntry),
    ...appSections.map(toSectionNavEntry),
    ...appResources.map(toResourceNavEntry),
  ];

  const appActors = input.actors.filter((a) => a.applicationCode === appCode);
  const actorCodesSet = new Set(appActors.map((a) => a.code));

  const roles: RoleEntry[] = appActors.map((actor) => ({
    name: actor.name,
    code: actor.code,
    applicationCode: actor.applicationCode,
    isDefault: actor.isDefault ?? false,
  }));

  const permissions: PermissionEntry[] = [];
  const rolePermissions: RolePermission[] = [];
  for (const perm of input.permissions) {
    const parsed = parsePermissionPath(perm.path);
    if (!parsed) continue;
    // Section codes can repeat across apps (e.g. "overview"), so verify the
    // permission's full path roots at this app's code AND its module belongs
    // here AND its section belongs here. All three must match before the
    // permission is attributed to this app.
    if (parsed.appCode !== appCode) continue;
    if (!moduleCodesSet.has(parsed.moduleCode)) continue;
    if (!sectionCodesSet.has(parsed.sectionCode)) continue;
    permissions.push({
      path: perm.path,
      action: parsed.action,
      sectionCode: parsed.sectionCode,
    });
    for (const roleCode of perm.roles) {
      if (!actorCodesSet.has(roleCode)) continue;
      rolePermissions.push({ roleCode, permissionPath: perm.path });
    }
  }

  // One test user per actor — Phase 5 UI tests need login per role.
  const testUsers: TestUserEntry[] = appActors.map((actor) => ({
    roleCode: actor.code,
  }));

  return {
    code: appCode,
    modules: moduleCodes,
    navigation,
    roles,
    permissions,
    rolePermissions,
    testUsers,
  };
}

// ─── Mappers ─────────────────────────────────────────────────────────────

function toAppNavEntry(a: MenuApplicationInput): NavigationEntry {
  return {
    level: 'application',
    code: a.code,
    label: a.label,
    icon: a.icon ?? DEFAULT_ICON,
    iconType: a.iconType ?? DEFAULT_ICON_TYPE,
    route: a.route ?? `/${a.code}`,
    displayOrder: a.displayOrder ?? 1,
    componentKey: a.componentKey,
  };
}

function toModuleNavEntry(m: MenuModuleInput): NavigationEntry {
  return {
    level: 'module',
    code: m.code,
    parentCode: m.applicationCode,
    label: m.label,
    icon: m.icon ?? DEFAULT_ICON,
    iconType: m.iconType ?? DEFAULT_ICON_TYPE,
    route: m.route ?? `/${m.applicationCode}/${m.code}`,
    displayOrder: m.displayOrder ?? 1,
    componentKey: m.componentKey,
  };
}

function toSectionNavEntry(s: MenuSectionInput): NavigationEntry {
  return {
    level: 'section',
    code: s.code,
    parentCode: s.moduleCode,
    label: s.label,
    icon: s.icon ?? DEFAULT_ICON,
    iconType: s.iconType ?? DEFAULT_ICON_TYPE,
    route: s.route ?? `/${s.moduleCode}/${s.code}`,
    displayOrder: s.displayOrder ?? 1,
    componentKey: s.componentKey,
  };
}

function toResourceNavEntry(r: MenuResourceInput): NavigationEntry {
  return {
    level: 'resource',
    code: r.code,
    parentCode: r.sectionCode,
    label: r.label,
    icon: r.icon ?? DEFAULT_ICON,
    iconType: r.iconType ?? DEFAULT_ICON_TYPE,
    route: r.route ?? `/${r.sectionCode}/${r.code}`,
    displayOrder: r.displayOrder ?? 1,
    componentKey: r.componentKey,
  };
}

/**
 * Parse a 4-segment permission path `{app}.{module}.{section}.{action}` into
 * its parts. Returns null when the path doesn't match — caller is expected to
 * have validated the shape upstream (audit-prd would have flagged it), so a
 * parse failure here is just a defensive skip.
 */
function parsePermissionPath(
  path: string,
): { appCode: string; moduleCode: string; sectionCode: string; action: string } | null {
  const parts = path.split('.');
  if (parts.length !== 4) return null;
  const [appCode, moduleCode, sectionCode, action] = parts;
  if (!appCode || !moduleCode || !sectionCode || !action) return null;
  return { appCode, moduleCode, sectionCode, action };
}
