import { useState, type ReactElement } from 'react';
import type { PendingApproval } from './use-chat-connection';
import { MAX_COMMAND_MESSAGE_CHARS } from './limits';
import { asNonEmptyString, asString } from './runtime-guards';
import type {
  CommandManifest,
  SlashCommandApprovalResultPayload,
  SlashCommandResultPayload,
} from '@/lib/chat-contract';

/** Stable fallback copy shown for a failed command only when the server's
 * own guarded, non-empty `message` (e.g. "Unknown model") is absent or
 * malformed — the structured contract reason itself is otherwise shown
 * directly, never a raw thrown exception, stack trace, or object value. */
const COMMAND_FAILURE_COPY = 'Command failed.';

/** Render-site defense-in-depth: `use-chat-connection.ts` already bounds a
 * stored command:result message at ingestion, but this component must never
 * assume every caller went through that path — bounding again here means a
 * hostile/oversized message can never force an unbounded render. */
function boundMessage(value: string): string {
  return value.length > MAX_COMMAND_MESSAGE_CHARS
    ? value.slice(0, MAX_COMMAND_MESSAGE_CHARS)
    : value;
}

interface CommandsPanelProps {
  manifest: CommandManifest | null;
  results: SlashCommandResultPayload[];
  approval: SlashCommandApprovalResultPayload | null;
  pendingApproval: PendingApproval | null;
  hasConversation: boolean;
  onExecute: (input: { command: string; args?: string }) => void;
  onApprove: (input: { command: string; args?: string }) => void;
  onRunApproved: () => void;
}

export function CommandsPanel({
  manifest,
  results,
  approval,
  pendingApproval,
  hasConversation,
  onExecute,
  onApprove,
  onRunApproved,
}: CommandsPanelProps): ReactElement {
  const [command, setCommand] = useState('');
  const [args, setArgs] = useState('');

  // Defense-in-depth: the reducer already normalizes success/approvalId
  // before storing `approval`, but a matching command string alone must
  // never be trusted here either — require the literal boolean `true` and a
  // non-empty string approvalId, not merely truthy values.
  const canRunApproved =
    approval?.success === true &&
    typeof approval.approvalId === 'string' &&
    approval.approvalId.length > 0 &&
    !!pendingApproval &&
    pendingApproval.command === approval.command;

  // A manifest arrives from the server as untyped JSON at runtime — guard
  // both collections before mapping so a malformed manifest cannot throw.
  const commands = Array.isArray(manifest?.commands) ? manifest.commands : [];
  const skills = Array.isArray(manifest?.skills) ? manifest.skills : [];

  return (
    <section aria-label="Commands" className="flex flex-col gap-2 border-b px-4 py-3 text-xs">
      {commands.length > 0 ? (
        <ul aria-label="Available commands" className="flex flex-col gap-1">
          {commands.map((cmd, index) => (
            <li key={asString(cmd?.name) || `cmd-${index}`}>
              <strong>/{asString(cmd?.name)}</strong> — {asString(cmd?.description)}
            </li>
          ))}
        </ul>
      ) : null}

      {skills.length > 0 ? (
        <ul aria-label="Available skills" className="flex flex-col gap-1">
          {skills.map((skill, index) => (
            <li key={asString(skill?.name) || `skill-${index}`}>
              <strong>/skill:{asString(skill?.name)}</strong> — {asString(skill?.description)}
            </li>
          ))}
        </ul>
      ) : null}

      <div className="flex flex-wrap items-center gap-2">
        <input
          aria-label="Command name"
          value={command}
          onChange={(event) => setCommand(event.target.value)}
          placeholder="command"
        />
        <input
          aria-label="Command arguments"
          value={args}
          onChange={(event) => setArgs(event.target.value)}
          placeholder="args (optional)"
        />
        <button
          type="button"
          disabled={!hasConversation || !command.trim()}
          onClick={() => onExecute({ command: command.trim(), args: args.trim() || undefined })}
        >
          Run command
        </button>
        <button
          type="button"
          disabled={!hasConversation || !command.trim()}
          onClick={() => onApprove({ command: command.trim(), args: args.trim() || undefined })}
        >
          Request approval
        </button>
      </div>

      {approval ? (
        <div role={approval.success ? 'status' : 'alert'} className="flex items-center gap-2">
          {/* A successful approval shows stable client copy only — never
              the server-controlled approval.message or echoed
              approval.command as the primary confirmation. The frozen local
              pendingApproval below (not this line) is the sole authoritative
              statement of what will run. A denial, by contrast, is not an
              execution authority and safely surfaces the guarded structured
              reason the server gave (e.g. "Not authorized"), falling back to
              a stable copy only when absent/malformed. */}
          <span>
            {approval.success ? 'Approved.' : asNonEmptyString(approval.message, 'Denied.')}
          </span>
          {canRunApproved && pendingApproval ? (
            <>
              {/* Authoritative frozen local command+args — what the click below
                  will actually emit. The server's `approval` above is display-only
                  and must never be trusted to represent the executed payload. */}
              <span>
                Will run: /{pendingApproval.command}{' '}
                {pendingApproval.args ? pendingApproval.args : '(no args)'}
              </span>
              <button type="button" onClick={onRunApproved}>
                Run approved command
              </button>
            </>
          ) : null}
        </div>
      ) : null}

      {results.length > 0 ? (
        <ul aria-label="Command results" className="flex flex-col gap-1">
          {results.map((result, index) => (
            <li key={`${result.command}-${index}`} role={result.success ? 'status' : 'alert'}>
              /{asString(result.command)}: {result.success ? 'success' : 'failed'}
              {result.success
                ? typeof result.message === 'string' && result.message
                  ? ` — ${boundMessage(result.message)}`
                  : ''
                : ` — ${boundMessage(asNonEmptyString(result.message, COMMAND_FAILURE_COPY))}`}
            </li>
          ))}
        </ul>
      ) : null}
    </section>
  );
}
