import { useState, type KeyboardEvent, type ReactElement } from 'react';
import type { HarnessSelection } from '@/lib/types';
import type { HarnessSelectionValue } from './use-harness-selection';

interface ComposerProps {
  onSend: (input: { content: string; selection: HarnessSelection }) => boolean;
  onStop: () => void;
  streaming: boolean;
  /** True from local send time through server turn startup/ack and
   * throughout streaming — a superset of `streaming` that also covers the
   * pre-ack window where a second send could otherwise slip through. */
  sending: boolean;
  hasConversation: boolean;
  /** Structured harness/provider/model selection state. The composer never
   * accepts free-text provider/model — every sendable tuple is a validated,
   * persisted catalog entry, and the send projection is derived from it. */
  harness: HarnessSelectionValue;
}

/** The distinct provider ids present in the current catalog, in first-seen
 * order — the provider select is catalog-derived, never a hardcoded list. */
function providerOptions(harness: HarnessSelectionValue): string[] {
  const seen = new Set<string>();
  const out: string[] = [];
  for (const model of harness.catalog?.models ?? []) {
    if (seen.has(model.providerId)) continue;
    seen.add(model.providerId);
    out.push(model.providerId);
  }
  return out;
}

export function Composer({
  onSend,
  onStop,
  streaming,
  sending,
  hasConversation,
  harness,
}: ComposerProps): ReactElement {
  const [content, setContent] = useState('');
  const busy = streaming || sending;

  function submit(): void {
    if (busy) return;
    // Send is gated on a validated, persisted catalog tuple — a draft or unset
    // selection can never emit, so provider/model never travel as free text.
    if (!harness.canSend || harness.persistedSelection === null) return;
    const trimmed = content.trim();
    if (!trimmed) return;
    // Pass the validated, persisted selection tuple only. The hook derives the
    // wire projection (legacy `message` provider/model, or `turn:send`) from the
    // negotiated `chat:send-capability` protocol — never from flat caller input.
    const selection = harness.persistedSelection;
    const ok = onSend({ content: trimmed, selection });
    // Clear the input only when the send was accepted — a refused turn (e.g. a
    // failed idempotency mint) must retain the user's text so it is not lost.
    if (ok) setContent('');
  }

  function handleKeyDown(event: KeyboardEvent<HTMLTextAreaElement>): void {
    if (event.key === 'Enter' && !event.shiftKey) {
      event.preventDefault();
      submit();
    }
  }

  // Scope the model options to the intentionally selected provider. With no
  // provider chosen (`providerId === ''`) nothing matches, so the model select
  // offers only the placeholder — never a cross-provider row.
  const models = (harness.catalog?.models ?? []).filter(
    (model) => model.providerId === harness.providerId,
  );
  // A collision-safe composite option identity covering the full provider+model
  // tuple. The controlled select mirrors the same identity so the exact catalog
  // row highlights (a bare modelId would collide across providers).
  const modelOptionValue = (model: { providerId: string; modelId: string }): string =>
    `${model.providerId}:${model.modelId}`;
  const selectedModelValue = harness.modelId ? `${harness.providerId}:${harness.modelId}` : '';

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        submit();
      }}
      className="flex flex-col gap-2 border-t p-4"
    >
      <div className="flex flex-wrap gap-2">
        <select
          aria-label="Harness"
          value={harness.harnessId}
          onChange={(event) => harness.selectHarness(event.target.value)}
          className="rounded border px-2 py-1 text-xs"
        >
          <option value="">Select a harness…</option>
          {harness.harnesses.map((item) => (
            <option key={item.id} value={item.id}>
              {item.displayName}
            </option>
          ))}
        </select>
        <select
          aria-label="Provider"
          value={harness.providerId}
          onChange={(event) => harness.selectProvider(event.target.value)}
          disabled={harness.catalogUnavailable || providerOptions(harness).length === 0}
          className="rounded border px-2 py-1 text-xs"
        >
          <option value="">Select a provider…</option>
          {providerOptions(harness).map((providerId) => (
            <option key={providerId} value={providerId}>
              {providerId}
            </option>
          ))}
        </select>
        <select
          aria-label="Model"
          value={selectedModelValue}
          onChange={(event) => {
            // Resolve the composite option identity back to the exact catalog
            // row and persist that row's own provider+model — never a bare id.
            const selected = models.find((model) => modelOptionValue(model) === event.target.value);
            if (selected) harness.selectModel(selected.providerId, selected.modelId);
          }}
          disabled={harness.catalogUnavailable || models.length === 0}
          className="rounded border px-2 py-1 text-xs"
        >
          <option value="">Select a model…</option>
          {models.map((model) => (
            <option key={modelOptionValue(model)} value={modelOptionValue(model)}>
              {model.displayName}
            </option>
          ))}
        </select>
      </div>
      {harness.catalogUnavailable ? (
        <p role="status" className="text-xs opacity-70">
          This harness catalog is currently unavailable.
        </p>
      ) : null}
      {harness.isStale ? (
        <p role="status" className="text-xs opacity-70">
          The saved model is no longer available — pick another to continue.
        </p>
      ) : null}
      {harness.persistError ? (
        <p role="alert" className="text-xs">
          {harness.persistError.message}
        </p>
      ) : null}
      <div className="flex items-end gap-2">
        <textarea
          aria-label="Message"
          value={content}
          onChange={(event) => setContent(event.target.value)}
          onKeyDown={handleKeyDown}
          rows={2}
          placeholder="Message… (Enter to send, Shift+Enter for a new line)"
          className="flex-1 resize-none rounded border px-3 py-2 text-sm"
        />
        <button
          type="submit"
          disabled={!content.trim() || busy || !harness.canSend}
          className="rounded px-3 py-2 text-sm font-medium"
        >
          Send
        </button>
        <button
          type="button"
          aria-label="Stop"
          disabled={!hasConversation || !streaming}
          onClick={onStop}
          className="rounded px-3 py-2 text-sm font-medium"
        >
          Stop
        </button>
      </div>
    </form>
  );
}
