// Build the Fabric slash-command catalog from OpenClaw's native-command
// specs (the same source Discord uses to register slash commands) and push
// it to each connected guild. Fabric is a TEXT-command surface: a /<cmd>
// message is delivered normally and OpenClaw's command system executes it —
// this catalog only drives the frontend `/` autocomplete, so we resolve any
// dynamic arg `choices` to a static snapshot here (like Discord does at
// registration time).
import {
  listNativeCommandSpecsForConfig,
  findCommandByNativeName,
  resolveCommandArgChoices,
} from 'openclaw/plugin-sdk/native-command-registry';
import type { FabricClient } from './fabric-client.js';
import { resolveCommandsSyncKey } from './accounts.js';

type Logger = { info: (m: string) => void; warn: (m: string) => void };

type FabricArg = {
  name: string;
  description: string;
  type: string;
  required: boolean;
  captureRemaining: boolean;
  preferAutocomplete: boolean;
  choices: Array<{ value: string; label: string }> | null;
};
type FabricCommand = {
  name: string;
  nativeName: string;
  description: string;
  acceptsArgs: boolean;
  args: FabricArg[];
  argsParsing: string;
};

function normChoice(c: unknown): { value: string; label: string } {
  if (typeof c === 'string') return { value: c, label: c };
  const o = c as { value?: string; label?: string };
  return { value: String(o.value ?? ''), label: String(o.label ?? o.value ?? '') };
}

export function buildFabricCommandSpecs(cfg: unknown): FabricCommand[] {
  const specs = listNativeCommandSpecsForConfig(cfg as never, {
    provider: 'fabric',
  }) as Array<{
    name: string;
    description: string;
    acceptsArgs?: boolean;
    args?: Array<Record<string, unknown>>;
  }>;

  return specs.map((s) => {
    // ChatCommandDefinition (for argsParsing + dynamic choices provider)
    const def = findCommandByNativeName(s.name, 'fabric') as
      | { argsParsing?: string; args?: Array<Record<string, unknown>> }
      | undefined;

    const args: FabricArg[] = (s.args ?? []).map((a) => {
      const raw = a.choices;
      let choices: Array<{ value: string; label: string }> | null = null;
      if (Array.isArray(raw)) {
        choices = raw.map(normChoice);
      } else if (typeof raw === 'function' && def) {
        try {
          const r = resolveCommandArgChoices({
            command: def as never,
            arg: a as never,
            cfg: cfg as never,
            provider: 'fabric',
          }) as Array<{ value: string; label: string }>;
          choices = r.map((x) => ({ value: x.value, label: x.label }));
        } catch {
          choices = null;
        }
      }
      return {
        name: String(a.name ?? ''),
        description: String(a.description ?? ''),
        type: String(a.type ?? 'string'),
        required: !!a.required,
        captureRemaining: !!a.captureRemaining,
        preferAutocomplete: !!a.preferAutocomplete,
        choices,
      };
    });

    return {
      name: s.name,
      nativeName: s.name,
      description: s.description,
      acceptsArgs: !!s.acceptsArgs,
      args,
      argsParsing: def?.argsParsing ?? 'positional',
    };
  });
}

// Push the catalog to every guild the known agents belong to (idempotent;
// the catalog is OpenClaw-global, so one PUT per guild is enough).
export async function syncFabricCommands(
  client: FabricClient,
  cfg: unknown,
  accounts: Array<{ agentId: string; fabricApiKey: string }>,
  log: Logger,
): Promise<void> {
  // Guild C-2: the sync key comes from the channel config only (schema
  // marks it required). Without it the guild rejects the catalog write.
  const syncKey = resolveCommandsSyncKey(cfg as never);
  if (!syncKey) {
    log.warn(
      'fabric: channels.fabric.commandsSyncKey is not set — skipping ' +
        'slash-command sync (set it to the guild FABRIC_BACKEND_GUILD_COMMANDS_SYNC_KEY)',
    );
    return;
  }

  let specs: FabricCommand[];
  try {
    specs = buildFabricCommandSpecs(cfg);
  } catch (err) {
    log.warn(`fabric: build command specs failed: ${String(err)}`);
    return;
  }
  if (!specs.length) return;

  const done = new Set<string>();
  for (const a of accounts) {
    let session;
    try {
      session = await client.agentLogin(a.fabricApiKey);
    } catch {
      continue;
    }
    for (const g of session.guilds) {
      if (done.has(g.nodeId)) continue;
      const tok = session.guildAccessTokens.find(
        (t) => t.guildNodeId === g.nodeId,
      )?.token;
      if (!tok) continue;
      try {
        await client.syncCommands(g.endpoint, tok, specs, syncKey);
        done.add(g.nodeId);
        log.info(`fabric: synced ${specs.length} slash command(s) -> ${g.nodeId}`);
      } catch (err) {
        log.warn(`fabric: command sync failed ${g.nodeId}: ${String(err)}`);
      }
    }
  }
}
