/**
 * scaffold-frontend-auth/generate.ts — Emits the canonical auth primitives.
 *
 * Three files, all under the project's `web/{appCode}-web/src/` tree :
 *   - `business/auth/auth-store.ts` — Zustand store (user, permissions,
 *     loading, lastFetchedAt).
 *   - `business/auth/useAuth.ts` — hook that bootstraps the store on first
 *     consumer mount + exposes `hasPermission(path)`.
 *   - `components/auth/PermissionGuard.tsx` — wraps children, returns null
 *     when the auth store is loading or the permission is missing.
 *
 * The hook expects the backend to expose `GET /api/auth/me` returning :
 *   { user: { id, email, displayName }, permissions: string[] }
 * Permissions are full canonical paths (e.g.
 * `restauration.carte.dishes.create` or
 * `restauration.carte.dishes.allergens.create`) and `hasPermission` does
 * a strict `permissions.includes(path)` match — no transformation.
 */
import type { GeneratedFile, ScaffoldFrontendAuthInput } from './types.js';

const HEADER = '/* generated by scaffold-frontend-auth — overwrite-safe ; add the line `// @customised` at the top to lock against regeneration */\n';

export function generate(spec: ScaffoldFrontendAuthInput): GeneratedFile[] {
  const webRoot = `web/${spec.appCode}-web/src`;
  return [
    { path: `${webRoot}/business/auth/auth-store.ts`, content: authStore() },
    { path: `${webRoot}/business/auth/useAuth.ts`, content: useAuthHook() },
    { path: `${webRoot}/components/auth/PermissionGuard.tsx`, content: permissionGuard() },
  ];
}

function authStore(): string {
  return `${HEADER}import { create } from 'zustand';

/**
 * Mirror of the backend \`UserInfo\` record returned by \`GET /api/auth/me\`
 * (see \`SmartStack.Application/Auth/DTOs/AuthDtos.cs:UserInfo\`). Fields
 * are kept flat to match the wire format verbatim — no remapping in the
 * fetch layer, so the entire payload is debuggable from a single
 * \`console.log(useAuthStore.getState().user)\`.
 */
export interface AuthUser {
  id: string;
  email: string;
  firstName: string | null;
  lastName: string | null;
  roles: string[];
  /** Array of full permission paths returned verbatim by the backend.
   *  Canonical shape: \`{app}.{module}.{section}[.{resource}].{action}\`
   *  (4 or 5 segments). \`useAuth().hasPermission()\` strips the leading
   *  appCode segment when comparing with the page-side path (which is
   *  3 segments for section-scoped, 4 for resource-scoped — see useAuth.ts).
   *  Mirrored at the store top level so hasPermission() does not need to
   *  dereference user. */
  permissions: string[];
  tenants: TenantSummary[];
}

export interface TenantSummary {
  id: string;
  slug: string;
  name: string;
}

export interface AuthState {
  user: AuthUser | null;
  /** Full permission paths granted to the current user.
   *  Format: \`{app}.{module}.{section}[.{resource}].{action}\` (4 or 5 segments).
   *  Mirrors \`user.permissions\` for fast lookup without dereferencing user. */
  permissions: string[];
  /** True between the first api.get call and its first response (success or error). */
  loading: boolean;
  /** Wall-clock of the last successful /api/auth/me response. 0 when never fetched. */
  lastFetchedAt: number;
  /** Last error message, when the bootstrap call failed. */
  error: string | null;
  setLoading: (v: boolean) => void;
  setSession: (u: AuthUser | null, perms: string[]) => void;
  setError: (e: string | null) => void;
  reset: () => void;
}

export const useAuthStore = create<AuthState>((set) => ({
  user: null,
  permissions: [],
  loading: false,
  lastFetchedAt: 0,
  error: null,
  setLoading: (loading) => set({ loading }),
  setSession: (user, permissions) => set({
    user,
    permissions,
    loading: false,
    lastFetchedAt: Date.now(),
    error: null,
  }),
  setError: (error) => set({ error, loading: false }),
  reset: () => set({
    user: null,
    permissions: [],
    loading: false,
    lastFetchedAt: 0,
    error: null,
  }),
}));
`;
}

function useAuthHook(): string {
  return `${HEADER}import { useEffect } from 'react';
import { api } from '@atlashub/smartstack';
import { useAuthStore, type AuthUser } from './auth-store';

let bootstrapPromise: Promise<void> | null = null;

/**
 * Bootstraps the auth store from \`GET /api/auth/me\`. Called by
 * useAuth() on first mount of any consumer. Subsequent calls are
 * no-ops while the first fetch is still in flight (single-flight via
 * bootstrapPromise).
 *
 * The backend returns \`UserInfo\` flat :
 *   { id, email, firstName, lastName, roles, permissions, tenants }
 * (see \`SmartStack.Application/Auth/DTOs/AuthDtos.cs\`). We store the
 * whole object as \`user\` and mirror \`permissions\` at the store top
 * level for fast hasPermission() lookups.
 */
async function bootstrap(): Promise<void> {
  if (bootstrapPromise) return bootstrapPromise;
  const store = useAuthStore.getState();
  if (store.lastFetchedAt > 0) return; // already loaded once this session
  store.setLoading(true);
  bootstrapPromise = (async () => {
    try {
      const resp = await api.get<AuthUser>('/api/auth/me');
      const permissions = Array.isArray(resp?.permissions) ? resp.permissions : [];
      useAuthStore.getState().setSession(resp ?? null, permissions);
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      useAuthStore.getState().setError(msg);
    } finally {
      bootstrapPromise = null;
    }
  })();
  return bootstrapPromise;
}

export interface UseAuthReturn {
  user: AuthUser | null;
  permissions: string[];
  isAuthenticated: boolean;
  loading: boolean;
  error: string | null;
  /**
   * True when the user holds the permission. Four match modes, tried in
   * order (first hit wins) :
   *
   * 1. **Super-admin wildcard** — \`permissions\` contains \`"*"\`. Always true.
   *    Mirrors SmartStack's \`WILDCARD_PERMISSION\` convention exported from
   *    \`@atlashub/smartstack/utils/permissions\`.
   * 2. **Fast strict match** — caller passed a fully-qualified path that
   *    matches a store entry verbatim (4-/5-seg with appCode).
   * 3. **Subtree wildcard** — a store entry like \`testv2.budgets.*\` or
   *    \`budgets.*\` covers any path under that prefix (canonical or stripped
   *    form, so the wildcard also matches the page-side 3-/4-seg input).
   * 4. **Strip-leading-segment match** — the BA convention emits page-side
   *    paths without the appCode segment (3-seg section / 4-seg resource).
   *    Strip the leading segment of each store entry and compare strictly.
   *
   * Page-side path shape (input to this function):
   *   - section-scoped : 3 segments \`{module}.{section}.{action}\`
   *   - resource-scoped : 4 segments \`{module}.{section}.{resource}.{action}\`
   *
   * Store-side shape (DB / \`permission.Path\` / \`/api/auth/me\` payload):
   *   - super-admin : \`["*"]\`
   *   - subtree wildcard : \`["testv2.budgets.*"]\`
   *   - section-scoped : \`["{app}.{module}.{section}.{action}"]\`
   *   - resource-scoped : \`["{app}.{module}.{section}.{resource}.{action}"]\`
   *
   * Case-sensitive across all passes.
   */
  hasPermission: (path: string) => boolean;
  /**
   * Force a re-fetch of /api/auth/me. Useful after a role change in the
   * admin UI ; otherwise the bootstrap-once default is enough.
   */
  refresh: () => Promise<void>;
}

export function useAuth(): UseAuthReturn {
  useEffect(() => {
    void bootstrap();
  }, []);
  const user = useAuthStore((s) => s.user);
  const permissions = useAuthStore((s) => s.permissions);
  const loading = useAuthStore((s) => s.loading);
  const error = useAuthStore((s) => s.error);

  const hasPermission = (path: string): boolean => {
    if (!path) return false;
    // 1. Super-admin shortcut. SmartStack convention : \`*\` = all permissions
    //    (\`@atlashub/smartstack/utils/permissions\` exports the same constant
    //    as WILDCARD_PERMISSION). Without this branch, a platform admin
    //    seeded with [\`"*"\`] sees every PermissionGuard return null and the
    //    UI renders blank — repro on run fbe07a0c.
    if (permissions.includes('*')) return true;
    // 2. Fast strict match : caller already passed a fully-qualified
    //    4-/5-seg path that matches a store entry verbatim.
    if (permissions.includes(path)) return true;
    // 3. & 4. Per-entry inspection — covers subtree wildcards
    //    (\`testv2.budgets.*\`) and the BA convention (page emits 3-seg
    //    section / 4-seg resource without the appCode prefix, store carries
    //    the canonical 4-/5-seg form). The \`indexOf('.')\` guards against
    //    single-segment store entries (defensive — should not occur with
    //    seeded data).
    return permissions.some((entry) => {
      // 3. Subtree wildcard. Match against both the canonical prefix
      //    (\`testv2.budgets.\`) and the page-side stripped prefix
      //    (\`budgets.\`) so the same store entry covers both shapes of
      //    input.
      if (entry.endsWith('.*')) {
        const prefix = entry.slice(0, -1); // keeps the trailing dot
        if (path.startsWith(prefix)) return true;
        const dot = prefix.indexOf('.');
        if (dot >= 0) {
          const strippedPrefix = prefix.slice(dot + 1);
          if (strippedPrefix && path.startsWith(strippedPrefix)) return true;
        }
        return false;
      }
      // 4. Strip leading appCode segment, compare strictly with input.
      const dot = entry.indexOf('.');
      if (dot < 0) return false;
      return entry.slice(dot + 1) === path;
    });
  };

  const refresh = async (): Promise<void> => {
    useAuthStore.getState().reset();
    bootstrapPromise = null;
    await bootstrap();
  };

  return {
    user,
    permissions,
    isAuthenticated: !!user,
    loading,
    error,
    hasPermission,
    refresh,
  };
}
`;
}

function permissionGuard(): string {
  return `${HEADER}import type { ReactNode } from 'react';
import { useAuth } from '@/business/auth/useAuth';

interface PermissionGuardProps {
  /**
   * Permission path the user must hold to see the children. **Page-side**
   * shape (no appCode prefix) :
   *   - section-scoped  : 3 segments \`{module}.{section}.{action}\`
   *   - resource-scoped : 4 segments \`{module}.{section}.{resource}.{action}\`
   *
   * The leading appCode segment is added by the auth store entries (which
   * carry the canonical \`permission.Path\` from the DB) and stripped at
   * \`hasPermission()\` time. Pages stay app-agnostic ; the store stays
   * canonical.
   */
  permission: string;
  children: ReactNode;
  /**
   * Optional placeholder rendered when the user lacks the permission.
   * Default: nothing (the gated block disappears entirely).
   */
  fallback?: ReactNode;
}

/**
 * Hides children when :
 *   - the auth bootstrap is still loading (no flash of forbidden UI),
 *   - the user is not authenticated,
 *   - or hasPermission(permission) returns false.
 *
 * This component does ONE thing on purpose. Business logic that
 * conditionally enables actions per row / per column / per state lives
 * in the consuming component, not here.
 */
export function PermissionGuard({
  permission,
  children,
  fallback = null,
}: PermissionGuardProps): ReactNode {
  const { loading, isAuthenticated, hasPermission } = useAuth();
  if (loading) return null;
  if (!isAuthenticated) return fallback;
  if (!hasPermission(permission)) return fallback;
  return children;
}

export default PermissionGuard;
`;
}
