import { useEffect, useRef, useState, type ReactNode } from 'react';
import { Archive, CalendarClock, PanelLeftOpen, Play, RotateCcw, Save } from 'lucide-react';
import {
  ApiError,
  createAutomation,
  invokeAutomation,
  listAutomationInvocations,
  listBranches,
  updateAutomation,
  type Automation,
  type AgentProfile,
  type AutomationInvocation,
  type AutomationInvocationPage,
  type BranchOption,
  type Environment,
  type ModelChoice,
  type ReasoningLevel,
  type RepositoryOption,
  type Session,
} from '../../api.js';
import { cn } from '../../lib/utils.js';
import { Button } from '../ui/button.js';
import { Card } from '../ui/card.js';
import { Input } from '../ui/input.js';
import { Textarea } from '../ui/textarea.js';
import {
  EnvironmentBranchOverridesEditor,
  environmentRepositoryKey,
  type EnvironmentBranchOverrides,
  type EnvironmentBranchOverrideRepository,
} from './environment-branch-overrides.js';
import {
  BranchPicker,
  CodebasePicker,
  codebaseEnvironmentValue,
  codebaseRepositoryValue,
  OptionPicker,
  parseCodebasePickerValue,
} from './option-picker.js';
import { formatDate, statusTextClass } from './shared.js';
import { defaultReasoningLevelLabel, REASONING_LEVEL_OPTIONS, reasoningLevelFromContext } from './reasoning-level.js';

type AutomationForm = {
  id: string;
  name: string;
  scheduleCron: string;
  profileId: string;
  environmentId: string;
  environmentRevisionPolicy: 'follow_latest' | 'pinned';
  environmentRevisionId: string;
  environmentRevisionNumber: number | null;
  environmentBranchOverrides: EnvironmentBranchOverrides;
  repository: string;
  branch: string;
  model: string;
  reasoningLevel: ReasoningLevel | '';
  prompt: string;
  enabled: boolean;
};

type AsyncState<T> = {
  data: T;
  loading: boolean;
  error: string;
};

const invocationHistoryPageSize = 20;

export function AutomationsPanel(props: {
  automation: Automation | null;
  automationsLoaded: boolean;
  automationsLoading: boolean;
  canCallApi: boolean;
  canCreateAutomations: boolean;
  canManageTenantResources: boolean;
  token: string;
  environmentOptions: Environment[];
  environmentOptionsLoading: boolean;
  environmentOptionsError: string;
  repositoryOptions: RepositoryOption[];
  repositoryOptionsLoading: boolean;
  repositoryOptionsError: string;
  modelChoices: ModelChoice[];
  agentProfiles?: AgentProfile[];
  defaultProfileId?: string | null;
  defaultReasoningLevel: ReasoningLevel | '';
  selectedAutomationId: string;
  showOpenSidebar: boolean;
  openSidebarLabel?: string;
  loadInvocationPage?: (input: {
    automationId: string;
    token: string;
    limit: number;
    cursor?: string;
  }) => Promise<AutomationInvocationPage>;
  onAutomationChanged: (automation: Automation) => void;
  onArchiveAutomation: (automationId: string) => void;
  onAutomationSaved: (automation: Automation) => void;
  onOpenSidebar: () => void;
  onSessionCreated: (session: Session) => void;
  onSelectSession: (sessionId: string) => void;
  onUnarchiveAutomation: (automationId: string) => void;
  onError: (error: unknown) => void;
}) {
  const [invocations, setInvocations] = useState<AutomationInvocation[]>([]);
  const [invocationsNextCursor, setInvocationsNextCursor] = useState('');
  const [invocationsLoading, setInvocationsLoading] = useState(false);
  const [olderInvocationsLoading, setOlderInvocationsLoading] = useState(false);
  const [saving, setSaving] = useState(false);
  const [form, setForm] = useState<AutomationForm>(emptyForm);
  const [branchControlsOpen, setBranchControlsOpen] = useState(false);
  const [branchOptionsState, setBranchOptionsState] = useState<AsyncState<BranchOption[]>>({
    data: [],
    loading: false,
    error: '',
  });
  const branchOptionsRepositoryRef = useRef('');
  const selected = props.automation;
  const selectedArchived = Boolean(selected?.archivedAt);
  const selectedAutomationIdRef = useRef(selected?.id ?? '');
  const environmentOptions = props.environmentOptions.filter((environment) => !environment.archivedAt);
  const selectedEnvironment = environmentOptions.find((environment) => environment.id === form.environmentId) ?? null;
  const codebaseValue = form.environmentId
    ? codebaseEnvironmentValue(form.environmentId)
    : form.repository
      ? codebaseRepositoryValue(form.repository)
      : '';
  const hasBranchControls = Boolean(form.repository || selectedEnvironment);
  const branchControlLabel = selectedEnvironment?.repositories.length === 1 || form.repository ? 'Branch' : 'Branches';
  const branchOptions = branchOptionsState.data;
  const branchOptionsLoading = branchOptionsState.loading;
  const branchOptionsError = branchOptionsState.error;
  const modelOptions = props.modelChoices.length
    ? props.modelChoices
    : [{ value: '', label: 'Default model', available: true }];
  const displayedModelOptions =
    form.model && !modelOptions.some((option) => option.value === form.model)
      ? [{ value: form.model, label: `${form.model} (saved)`, available: true }, ...modelOptions]
      : modelOptions;
  const selectableAgentProfiles = (props.agentProfiles ?? []).filter(
    (profile) => profile.enabled && !profile.archivedAt && profile.supportedInvocations.includes('agent'),
  );
  const savedAgentProfile = (props.agentProfiles ?? []).find((profile) => profile.id === form.profileId);
  const savedAgentProfileUnavailable = Boolean(
    selected &&
    form.profileId &&
    (!savedAgentProfile ||
      !savedAgentProfile.enabled ||
      savedAgentProfile.archivedAt ||
      !savedAgentProfile.supportedInvocations.includes('agent')),
  );
  const agentProfileOptions = [
    ...(savedAgentProfileUnavailable
      ? [{ value: form.profileId, label: `${savedAgentProfile?.name ?? form.profileId} (unavailable)` }]
      : []),
    ...selectableAgentProfiles.map((profile) => ({ value: profile.id, label: profile.name })),
  ];
  const canEdit =
    props.canCallApi &&
    props.canManageTenantResources &&
    (selected ? Boolean(selected.canManage) && !selectedArchived : props.canCreateAutomations);
  const canEditDefinition = canEdit;
  const formComplete = Boolean(form.name.trim() && form.scheduleCron.trim() && form.profileId && form.prompt.trim());
  const saveDisabled = !canEdit || saving || !formComplete;
  const revisionPolicyHasUnsavedChanges =
    !selected ||
    form.environmentId !== (selected.environmentId ?? '') ||
    form.environmentRevisionPolicy !== (selected.environmentRevisionPolicy ?? 'follow_latest') ||
    form.environmentRevisionId !== (selected.environmentRevisionId ?? '');
  const revisionPolicySaveHint = revisionPolicyHasUnsavedChanges
    ? selected
      ? ' Save to apply this change.'
      : ' Create automation to apply this setting.'
    : '';

  useEffect(() => {
    selectedAutomationIdRef.current = selected?.id ?? '';
  }, [selected?.id]);

  useEffect(() => {
    setBranchControlsOpen(false);
  }, [codebaseValue]);

  useEffect(() => {
    if (!selected) {
      setInvocations([]);
      setInvocationsNextCursor('');
      setInvocationsLoading(false);
      setOlderInvocationsLoading(false);
      if (!props.selectedAutomationId) setForm(emptyForm(props.defaultProfileId ?? ''));
      return;
    }
    let cancelled = false;
    setForm(formFromAutomation(selected));
    setInvocationsLoading(true);
    setOlderInvocationsLoading(false);
    setInvocationsNextCursor('');
    const loadInvocationPage = props.loadInvocationPage ?? listAutomationInvocations;
    loadInvocationPage({ automationId: selected.id, token: props.token, limit: invocationHistoryPageSize })
      .then((page) => {
        if (cancelled) return;
        setInvocations(page.invocations);
        setInvocationsNextCursor(page.nextCursor ?? '');
      })
      .catch((error) => {
        if (!cancelled) props.onError(error);
      })
      .finally(() => {
        if (!cancelled) setInvocationsLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [selected?.id, props.selectedAutomationId, props.token, props.loadInvocationPage]);

  useEffect(() => {
    if (!selected && !props.selectedAutomationId && props.defaultProfileId) {
      setForm((current) =>
        !current.id && !current.profileId ? { ...current, profileId: props.defaultProfileId ?? '' } : current,
      );
    }
  }, [selected, props.selectedAutomationId, props.defaultProfileId]);

  useEffect(() => {
    const repository = form.repository.trim();
    if (branchOptionsRepositoryRef.current !== repository) {
      branchOptionsRepositoryRef.current = repository;
      setBranchOptionsState((current) => ({ ...current, data: [], error: '' }));
    }
    if (!props.canCallApi || form.environmentId || !repository) {
      setBranchOptionsState((current) => ({ ...current, loading: false }));
      return;
    }

    let cancelled = false;
    setBranchOptionsState((current) => ({ ...current, loading: true, error: '' }));
    listBranches({ repository, token: props.token })
      .then((branches) => {
        if (cancelled) return;
        setBranchOptionsState({ data: branches, loading: false, error: '' });
        setForm((current) => {
          if (current.repository.trim() !== repository || current.branch) return current;
          const repo = props.repositoryOptions.find((option) => option.fullName === repository);
          return { ...current, branch: repo?.defaultBranch ?? branches[0]?.name ?? '' };
        });
      })
      .catch(() => {
        if (!cancelled) setBranchOptionsState({ data: [], loading: false, error: 'Could not load branches.' });
      });

    return () => {
      cancelled = true;
    };
  }, [form.environmentId, form.repository, props.canCallApi, props.repositoryOptions, props.token]);

  async function saveAutomation() {
    if (saveDisabled) return;
    setSaving(true);
    try {
      const input = automationFormInput(form, props.token, selectedEnvironment);
      let saved: Automation;
      if (form.id) {
        saved = await updateAutomation({ ...input, automationId: form.id });
      } else {
        saved = await createAutomation(input);
      }
      props.onAutomationSaved(saved);
      setForm(formFromAutomation(saved));
    } catch (error) {
      props.onError(error);
    } finally {
      setSaving(false);
    }
  }

  async function toggleEnabled(automation: Automation) {
    if (!props.canCallApi || !automation.canManage || automation.archivedAt) return;
    setSaving(true);
    try {
      const updated = await updateAutomation({
        automationId: automation.id,
        token: props.token,
        enabled: !automation.enabled,
      });
      props.onAutomationChanged(updated);
      if (selected?.id === updated.id) setForm(formFromAutomation(updated));
    } catch (error) {
      props.onError(error);
    } finally {
      setSaving(false);
    }
  }

  async function invokeSelected(options: { allowOverlap?: boolean; disabledConfirmed?: boolean } = {}) {
    if (!props.canCallApi || !selected?.canManage || selected.archivedAt) return;
    const allowDisabled = !selected.enabled;
    const allowOverlap = options.allowOverlap === true;
    if (
      allowDisabled &&
      !options.disabledConfirmed &&
      !window.confirm('This automation is disabled. Invoke it once anyway?')
    ) {
      return;
    }
    setSaving(true);
    try {
      const result = await invokeAutomation({
        automationId: selected.id,
        token: props.token,
        allowDisabled,
        allowOverlap,
      });
      props.onAutomationChanged(result.automation);
      setInvocations((current) => [
        result.invocation,
        ...current.filter((invocation) => invocation.id !== result.invocation.id),
      ]);
      if (result.session) props.onSessionCreated(result.session);
    } catch (error) {
      if (error instanceof ApiError && error.status === 409 && !allowOverlap) {
        const confirmed = window.confirm(
          'This automation already has a queued or active session. Invoke another session anyway?',
        );
        if (confirmed) await invokeSelected({ allowOverlap: true, disabledConfirmed: allowDisabled });
      } else {
        props.onError(error);
      }
    } finally {
      setSaving(false);
    }
  }

  async function loadOlderInvocations() {
    if (!selected || !invocationsNextCursor || olderInvocationsLoading) return;
    const automationId = selected.id;
    setOlderInvocationsLoading(true);
    try {
      const page = await (props.loadInvocationPage ?? listAutomationInvocations)({
        automationId,
        token: props.token,
        limit: invocationHistoryPageSize,
        cursor: invocationsNextCursor,
      });
      if (selectedAutomationIdRef.current !== automationId) return;
      setInvocations((current) => [
        ...current,
        ...page.invocations.filter((invocation) => !current.some((existing) => existing.id === invocation.id)),
      ]);
      setInvocationsNextCursor(page.nextCursor ?? '');
    } catch (error) {
      props.onError(error);
    } finally {
      setOlderInvocationsLoading(false);
    }
  }

  return (
    <section className="h-full min-h-0 overflow-auto px-4 py-6 md:px-8 xl:px-14">
      <div className="mx-auto grid max-w-4xl gap-5">
        <div className="flex flex-wrap items-start justify-between gap-3">
          <div className="flex min-w-0 items-start gap-2">
            {props.showOpenSidebar ? (
              <Button
                className="mt-1 h-8 w-8 shrink-0 p-0 md:hidden"
                variant="ghost"
                size="icon"
                onClick={props.onOpenSidebar}
                aria-label={props.openSidebarLabel ?? 'Open sessions'}
                title={props.openSidebarLabel ?? 'Open sessions'}
              >
                <PanelLeftOpen className="h-4 w-4" />
              </Button>
            ) : null}
            <div className="min-w-0">
              <p className="text-xs font-semibold uppercase tracking-widest text-primary">Automations</p>
              <h1 className="mt-1 text-2xl font-semibold tracking-tight text-foreground">Scheduled automations</h1>
              <p className="mt-1 text-sm text-muted-foreground">
                Create recurring sessions from 5-field cron expressions. Cron is always evaluated in UTC.
              </p>
            </div>
          </div>
        </div>

        {props.selectedAutomationId && !selected ? (
          <Card className="p-5">
            <h2 className="text-lg font-semibold text-foreground">
              {!props.automationsLoaded || props.automationsLoading ? 'Loading automation' : 'Automation not found'}
            </h2>
            <p className="mt-2 text-sm text-muted-foreground">
              {!props.automationsLoaded || props.automationsLoading
                ? 'Fetching the selected automation.'
                : 'The selected automation is not available or you no longer have access to it.'}
            </p>
          </Card>
        ) : (
          <div className="grid gap-5">
            <Card className="p-5">
              {/* Keep this panel kind-aware when non-scheduled automation types are added. */}
              <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-3">
                <div className="min-w-0 flex-1 basis-64">
                  <h2 className="text-lg font-semibold text-foreground">
                    {form.id ? 'Edit automation' : 'New automation'}
                  </h2>
                  <p className="mt-1 text-sm text-muted-foreground">
                    Generated sessions use this automation&apos;s prompt and codebase context.
                  </p>
                </div>
                {props.canManageTenantResources && selected && !selected.archivedAt ? (
                  <div className="flex shrink-0 flex-wrap items-center gap-2">
                    <Button
                      variant="secondary"
                      onClick={() => void toggleEnabled(selected)}
                      disabled={!props.canCallApi || saving || !selected.canManage}
                    >
                      {selected.enabled ? 'Disable' : 'Enable'}
                    </Button>
                    <Button
                      onClick={() => void invokeSelected()}
                      disabled={!props.canCallApi || saving || !selected.canManage}
                    >
                      <Play className="h-4 w-4" /> Invoke now
                    </Button>
                  </div>
                ) : null}
              </div>

              {selectedArchived ? (
                <div className="mt-4 rounded-md border border-border bg-muted/60 p-3 text-sm text-muted-foreground">
                  This automation is archived. Restore it before editing or invoking it. Restored automations stay
                  disabled until you enable them.
                </div>
              ) : null}

              <form
                className="mt-5 grid gap-4"
                onSubmit={(event) => {
                  event.preventDefault();
                  void saveAutomation();
                }}
              >
                <div className="grid gap-3">
                  <Field label="Name" htmlFor="automation-name">
                    <Input
                      id="automation-name"
                      value={form.name}
                      onChange={(event) => setForm({ ...form, name: event.target.value })}
                      placeholder="Nightly dependency check"
                      disabled={!canEditDefinition}
                    />
                  </Field>
                </div>

                <div className="grid gap-3 md:grid-cols-[minmax(10rem,1fr)_minmax(10rem,1fr)]">
                  <Field label="UTC cron" htmlFor="automation-cron" hint="Five fields: minute hour day month weekday.">
                    <Input
                      id="automation-cron"
                      className="font-mono"
                      value={form.scheduleCron}
                      onChange={(event) => setForm({ ...form, scheduleCron: event.target.value })}
                      placeholder="0 9 * * 1-5"
                      disabled={!canEditDefinition}
                    />
                  </Field>
                  <label className="mt-5 flex h-10 items-center gap-2 text-sm text-muted-foreground">
                    <input
                      className="disabled:cursor-not-allowed disabled:opacity-50"
                      type="checkbox"
                      checked={form.enabled}
                      onChange={(event) => setForm({ ...form, enabled: event.target.checked })}
                      disabled={!canEditDefinition}
                    />
                    Enabled for automatic scheduled invocations
                  </label>
                </div>

                <div className="grid items-start gap-3 xl:grid-cols-4">
                  <div className="order-1 xl:order-none">
                    <Field label="Agent" htmlFor="automation-agent">
                      <OptionPicker
                        id="automation-agent"
                        label="Agent"
                        value={form.profileId}
                        options={agentProfileOptions}
                        emptyLabel="Select agent"
                        onChange={(value) => setForm({ ...form, profileId: value })}
                        disabled={!canEditDefinition}
                      />
                      {savedAgentProfileUnavailable ? (
                        <p className="mt-1.5 text-xs font-medium text-destructive">
                          This agent is unavailable. Select an active agent before saving or invoking this automation.
                        </p>
                      ) : null}
                    </Field>
                  </div>
                  <div className="order-1 xl:order-none">
                    <Field label="Codebase" htmlFor="automation-codebase">
                      <CodebasePicker
                        id="automation-codebase"
                        value={codebaseValue}
                        environments={environmentOptions}
                        environmentsLoading={props.environmentOptionsLoading}
                        environmentsError={props.environmentOptionsError}
                        repositories={props.repositoryOptions}
                        repositoriesLoading={props.repositoryOptionsLoading}
                        repositoriesError={props.repositoryOptionsError}
                        onChange={(value) => setForm({ ...form, ...automationCodebaseFormPatch(value) })}
                        placeholder="Select environment or repository..."
                        disabled={!canEditDefinition}
                      />
                    </Field>
                  </div>
                  <div className="order-3 xl:order-none">
                    <Field label="Model" htmlFor="automation-model">
                      <OptionPicker
                        id="automation-model"
                        label="Model"
                        value={form.model}
                        options={displayedModelOptions}
                        emptyLabel="Default model"
                        allowEmpty={Boolean(form.model)}
                        onChange={(value) => setForm({ ...form, model: value })}
                        disabled={!canEditDefinition || props.modelChoices.length <= 1}
                      />
                    </Field>
                  </div>
                  <div className="order-4 xl:order-none">
                    <Field label="Reasoning" htmlFor="automation-reasoning">
                      <OptionPicker
                        id="automation-reasoning"
                        label="Reasoning"
                        value={form.reasoningLevel}
                        options={REASONING_LEVEL_OPTIONS}
                        emptyLabel={defaultReasoningLevelLabel(props.defaultReasoningLevel)}
                        allowEmpty={Boolean(form.reasoningLevel)}
                        onChange={(value) => setForm({ ...form, reasoningLevel: value as ReasoningLevel | '' })}
                        disabled={!canEditDefinition}
                      />
                    </Field>
                  </div>
                  {selectedEnvironment || hasBranchControls ? (
                    <div className="order-2 grid gap-3 xl:order-none xl:col-span-3">
                      {selectedEnvironment ? (
                        <div className="max-w-2xl">
                          <EnvironmentRevisionPolicy
                            environment={selectedEnvironment}
                            form={form}
                            disabled={!canEditDefinition}
                            saveHint={revisionPolicySaveHint}
                            onChange={(policy) =>
                              setForm({
                                ...form,
                                environmentRevisionPolicy: policy,
                                environmentRevisionId: policy === 'pinned' ? selectedEnvironment.currentRevisionId : '',
                                environmentRevisionNumber:
                                  policy === 'pinned' ? selectedEnvironment.currentRevisionNumber : null,
                              })
                            }
                          />
                        </div>
                      ) : null}
                      {hasBranchControls ? (
                        <div>
                          <Button
                            className="h-10 px-3"
                            type="button"
                            variant={branchControlsOpen ? 'default' : 'secondary'}
                            size="sm"
                            onClick={() => setBranchControlsOpen((open) => !open)}
                            disabled={!canEditDefinition}
                            aria-expanded={branchControlsOpen}
                          >
                            {branchControlLabel}
                          </Button>
                        </div>
                      ) : null}
                      {branchControlsOpen && form.repository ? (
                        <div className="max-w-xs min-w-0">
                          <BranchPicker
                            id="automation-branch"
                            value={form.branch}
                            branches={branchOptions}
                            loading={branchOptionsLoading}
                            error={branchOptionsError}
                            onChange={(value) => setForm({ ...form, branch: value })}
                            placeholder="Default"
                            disabled={!canEditDefinition}
                          />
                        </div>
                      ) : null}
                      {branchControlsOpen && selectedEnvironment ? (
                        <EnvironmentBranchOverridesEditor
                          environment={selectedEnvironment}
                          value={form.environmentBranchOverrides}
                          disabled={!canEditDefinition}
                          onLoadBranches={(repository) =>
                            loadAutomationEnvironmentRepositoryBranches(repository, props.token)
                          }
                          onChange={(value) => setForm({ ...form, environmentBranchOverrides: value })}
                        />
                      ) : null}
                    </div>
                  ) : null}
                </div>

                <Field label="Prompt" htmlFor="automation-prompt">
                  <Textarea
                    id="automation-prompt"
                    className="min-h-40"
                    value={form.prompt}
                    onChange={(event) => setForm({ ...form, prompt: event.target.value })}
                    placeholder="Ask Deputies to run this recurring check..."
                    disabled={!canEditDefinition}
                  />
                </Field>

                {!formComplete ? (
                  <p className="text-sm text-muted-foreground">Name, UTC cron, and prompt are required.</p>
                ) : null}
                <div className="flex flex-wrap gap-2">
                  <Button type="submit" disabled={saveDisabled}>
                    <Save className="h-4 w-4" /> {form.id ? 'Save automation' : 'Create automation'}
                  </Button>
                  {props.canManageTenantResources && selected ? (
                    <AutomationArchiveAction
                      automation={selected}
                      disabled={!props.canCallApi}
                      saving={saving}
                      onArchiveAutomation={props.onArchiveAutomation}
                      onUnarchiveAutomation={props.onUnarchiveAutomation}
                    />
                  ) : null}
                </div>
              </form>
            </Card>

            <Card className="p-5">
              <div className="flex items-center gap-2">
                <CalendarClock className="h-4 w-4 text-primary" />
                <h2 className="text-lg font-semibold text-foreground">Invocation history</h2>
              </div>
              {selected ? (
                <div className="mt-4 grid gap-2">
                  {invocationsLoading ? (
                    <p className="text-sm text-muted-foreground">Loading invocation history...</p>
                  ) : null}
                  {!invocationsLoading
                    ? invocations.map((invocation) => (
                        <InvocationRow
                          key={invocation.id}
                          invocation={invocation}
                          onSelectSession={props.onSelectSession}
                        />
                      ))
                    : null}
                  {!invocationsLoading && !invocations.length ? (
                    <p className="text-sm text-muted-foreground">No invocations recorded yet.</p>
                  ) : null}
                  {!invocationsLoading && invocationsNextCursor ? (
                    <Button
                      className="justify-self-start"
                      variant="secondary"
                      size="sm"
                      onClick={() => void loadOlderInvocations()}
                      disabled={olderInvocationsLoading}
                    >
                      {olderInvocationsLoading ? 'Loading older...' : 'Load older'}
                    </Button>
                  ) : null}
                </div>
              ) : (
                <p className="mt-4 text-sm text-muted-foreground">
                  Select an automation to inspect created, skipped, or failed invocations.
                </p>
              )}
            </Card>
          </div>
        )}
      </div>
    </section>
  );
}

function Field(props: { label: string; htmlFor: string; hint?: string; children: ReactNode }) {
  return (
    <div>
      <label className="mb-1 block text-xs font-medium text-muted-foreground" htmlFor={props.htmlFor}>
        {props.label}
      </label>
      {props.children}
      {props.hint ? <p className="mt-1 text-xs text-muted-foreground">{props.hint}</p> : null}
    </div>
  );
}

function EnvironmentRevisionPolicy(props: {
  environment: Environment;
  form: AutomationForm;
  disabled: boolean;
  saveHint: string;
  onChange: (policy: AutomationForm['environmentRevisionPolicy']) => void;
}) {
  const pinnedRevisionNumber = props.form.environmentRevisionNumber ?? props.environment.currentRevisionNumber;
  const pinIsCurrent =
    props.form.environmentRevisionPolicy !== 'pinned' ||
    props.form.environmentRevisionId === props.environment.currentRevisionId;
  return (
    <div className="grid gap-1">
      <span id="automation-environment-revision-policy" className="text-xs font-medium text-muted-foreground">
        Environment revision policy
      </span>
      <fieldset
        aria-labelledby="automation-environment-revision-policy"
        className="grid gap-2 rounded-md border border-border bg-background/70 p-3 text-sm text-muted-foreground"
      >
        <label className="flex cursor-pointer items-start gap-2">
          <input
            className="mt-0.5 disabled:cursor-not-allowed disabled:opacity-50"
            type="radio"
            name="environment-revision-policy"
            checked={props.form.environmentRevisionPolicy === 'follow_latest'}
            onChange={() => props.onChange('follow_latest')}
            disabled={props.disabled}
          />
          <span>
            <span className="block font-medium text-foreground">Follow latest</span>
            Use the latest environment revision at the time of invocation.
          </span>
        </label>
        <label className="flex cursor-pointer items-start gap-2">
          <input
            className="mt-0.5 disabled:cursor-not-allowed disabled:opacity-50"
            type="radio"
            name="environment-revision-policy"
            checked={props.form.environmentRevisionPolicy === 'pinned'}
            onChange={() => props.onChange('pinned')}
            disabled={props.disabled}
          />
          <span>
            <span className="block font-medium text-foreground">
              <strong>Pin</strong>
              {pinIsCurrent ? ' current ' : ' '}
              <strong>environment revision ({pinnedRevisionNumber})</strong>
            </span>
            Use this same environment revision for every invocation.
          </span>
        </label>
        {props.saveHint ? <p className="text-xs text-amber-700">{props.saveHint.trim()}</p> : null}
      </fieldset>
    </div>
  );
}

function AutomationArchiveAction(props: {
  automation: Automation;
  disabled: boolean;
  saving: boolean;
  onArchiveAutomation: (automationId: string) => void;
  onUnarchiveAutomation: (automationId: string) => void;
}) {
  if (props.automation.archivedAt) {
    return (
      <Button
        type="button"
        variant="secondary"
        onClick={() => props.onUnarchiveAutomation(props.automation.id)}
        disabled={props.disabled || props.saving || !props.automation.canManage}
      >
        <RotateCcw className="h-4 w-4" /> Restore automation
      </Button>
    );
  }

  return (
    <Button
      type="button"
      className="border-destructive/30 bg-transparent text-destructive hover:bg-destructive/10"
      variant="secondary"
      onClick={() => props.onArchiveAutomation(props.automation.id)}
      disabled={props.disabled || props.saving || !props.automation.canManage}
    >
      <Archive className="h-4 w-4" /> Archive automation
    </Button>
  );
}

function InvocationRow(props: { invocation: AutomationInvocation; onSelectSession: (sessionId: string) => void }) {
  const statusParts = [
    props.invocation.sessionStatus ? (
      <span key="session">
        Session{' '}
        <span className={statusTextClass(props.invocation.sessionStatus)}>{props.invocation.sessionStatus}</span>
      </span>
    ) : null,
    props.invocation.messageStatus ? (
      <span key="message">
        Message{' '}
        <span className={statusTextClass(props.invocation.messageStatus)}>{props.invocation.messageStatus}</span>
      </span>
    ) : null,
  ].filter(Boolean);

  return (
    <div className="rounded-md border border-border bg-background/70 px-3 py-2 text-sm">
      <div className="flex flex-wrap items-center justify-between gap-x-3 gap-y-1.5">
        <div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
          <span className={cn('font-semibold', statusTextClass(props.invocation.status))}>
            {props.invocation.status}
          </span>
          <span className="text-xs text-muted-foreground">{props.invocation.trigger}</span>
          <span className="text-xs text-muted-foreground">{formatDate(props.invocation.createdAt)}</span>
        </div>
        {props.invocation.sessionId ? (
          <Button
            className="h-7 px-2 text-xs"
            variant="secondary"
            size="sm"
            onClick={() => props.onSelectSession(props.invocation.sessionId!)}
          >
            Open session
          </Button>
        ) : null}
      </div>
      {statusParts.length ? (
        <div className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground">
          {statusParts.map((part, index) => (
            <span key={index}>
              {index > 0 ? <span className="mr-3 text-muted-foreground">|</span> : null}
              {part}
            </span>
          ))}
        </div>
      ) : null}
      {props.invocation.reason ? (
        <p className="mt-1 text-xs text-muted-foreground">Reason: {props.invocation.reason}</p>
      ) : null}
      {props.invocation.error ? <p className="mt-1 text-xs text-destructive">{props.invocation.error}</p> : null}
    </div>
  );
}

function emptyForm(profileId = ''): AutomationForm {
  return {
    id: '',
    name: '',
    scheduleCron: '0 9 * * 1-5',
    profileId,
    environmentId: '',
    environmentRevisionPolicy: 'follow_latest',
    environmentRevisionId: '',
    environmentRevisionNumber: null,
    environmentBranchOverrides: {},
    repository: '',
    branch: '',
    model: '',
    reasoningLevel: '',
    prompt: '',
    enabled: true,
  };
}

function formFromAutomation(automation: Automation): AutomationForm {
  return {
    id: automation.id,
    name: automation.name,
    scheduleCron: automation.scheduleCron,
    profileId: automation.profileId,
    environmentId: automation.environmentId ?? '',
    environmentRevisionPolicy: automation.environmentRevisionPolicy ?? 'follow_latest',
    environmentRevisionId: automation.environmentRevisionId ?? '',
    environmentRevisionNumber: automation.environmentRevisionNumber ?? null,
    environmentBranchOverrides: environmentBranchOverridesContextValue(automation.context?.environmentBranchOverrides),
    repository: repositoryContextLabel(automation.context?.repository),
    branch: typeof automation.context?.branch === 'string' ? automation.context.branch : '',
    model: typeof automation.context?.model === 'string' ? automation.context.model : '',
    reasoningLevel: reasoningLevelFromContext(automation.context?.reasoningLevel),
    prompt: automation.prompt,
    enabled: automation.enabled,
  };
}

function automationFormInput(form: AutomationForm, token: string, environment: Environment | null) {
  return {
    token,
    name: form.name.trim(),
    prompt: form.prompt.trim(),
    scheduleCron: form.scheduleCron.trim(),
    profileId: form.profileId,
    enabled: form.enabled,
    environmentId: form.environmentId,
    ...(form.environmentId
      ? {
          environmentRevisionPolicy: form.environmentRevisionPolicy,
          ...(form.environmentRevisionPolicy === 'pinned'
            ? { environmentRevisionId: form.environmentRevisionId || environment?.currentRevisionId || '' }
            : {}),
        }
      : {}),
    ...(form.environmentId
      ? {
          environmentBranchOverrides: automationEnvironmentBranchOverrideInput(
            form.environmentBranchOverrides,
            environment,
          ),
        }
      : {}),
    repository: form.environmentId ? '' : form.repository.trim(),
    branch: form.environmentId ? '' : form.branch.trim(),
    model: form.model,
    reasoningLevel: form.reasoningLevel,
  };
}

function automationCodebaseFormPatch(
  value: string,
): Pick<
  AutomationForm,
  | 'environmentId'
  | 'environmentRevisionPolicy'
  | 'environmentRevisionId'
  | 'environmentRevisionNumber'
  | 'environmentBranchOverrides'
  | 'repository'
  | 'branch'
> {
  const selection = parseCodebasePickerValue(value);
  if (selection?.kind === 'environment') {
    return {
      environmentId: selection.environmentId,
      environmentRevisionPolicy: 'follow_latest',
      environmentRevisionId: '',
      environmentRevisionNumber: null,
      environmentBranchOverrides: {},
      repository: '',
      branch: '',
    };
  }
  if (selection?.kind === 'repository') {
    return {
      environmentId: '',
      environmentRevisionPolicy: 'follow_latest',
      environmentRevisionId: '',
      environmentRevisionNumber: null,
      environmentBranchOverrides: {},
      repository: selection.repository,
      branch: '',
    };
  }
  return {
    environmentId: '',
    environmentRevisionPolicy: 'follow_latest',
    environmentRevisionId: '',
    environmentRevisionNumber: null,
    environmentBranchOverrides: {},
    repository: '',
    branch: '',
  };
}

function repositoryContextLabel(value: unknown): string {
  if (!value || typeof value !== 'object' || Array.isArray(value)) return '';
  const repository = value as Record<string, unknown>;
  const owner = typeof repository.owner === 'string' ? repository.owner : '';
  const repo = typeof repository.repo === 'string' ? repository.repo : '';
  return owner && repo ? `${owner}/${repo}` : '';
}

async function loadAutomationEnvironmentRepositoryBranches(
  repository: EnvironmentBranchOverrideRepository,
  token: string,
): Promise<BranchOption[]> {
  return listBranches({ repository: `${repository.owner}/${repository.repo}`, token });
}

function automationEnvironmentBranchOverrideInput(
  overrides: EnvironmentBranchOverrides,
  environment: Environment | null,
) {
  const repositories = new Map(
    (environment?.repositories ?? []).map((repository) => [environmentRepositoryKey(repository), repository]),
  );
  return Object.entries(overrides)
    .map(([key, branch]) => {
      const repository = repositories.get(key) ?? repositoryFromEnvironmentRepositoryKey(key);
      const trimmedBranch = branch.trim();
      if (!repository || !trimmedBranch) return null;
      return {
        provider: 'github' as const,
        owner: repository.owner,
        repo: repository.repo,
        branch: trimmedBranch,
      };
    })
    .filter((override): override is { provider: 'github'; owner: string; repo: string; branch: string } =>
      Boolean(override),
    );
}

function environmentBranchOverridesContextValue(value: unknown): EnvironmentBranchOverrides {
  if (!Array.isArray(value)) return {};
  return value.reduce<EnvironmentBranchOverrides>((overrides, item) => {
    if (!item || typeof item !== 'object' || Array.isArray(item)) return overrides;
    const record = item as Record<string, unknown>;
    const provider = record.provider === 'github' || record.provider === undefined ? 'github' : null;
    const owner = typeof record.owner === 'string' ? record.owner : '';
    const repo = typeof record.repo === 'string' ? record.repo : '';
    const branch = typeof record.branch === 'string' ? record.branch : '';
    if (!provider || !owner || !repo || !branch) return overrides;
    overrides[environmentRepositoryKey({ provider, owner, repo })] = branch;
    return overrides;
  }, {});
}

function repositoryFromEnvironmentRepositoryKey(key: string): { owner: string; repo: string } | null {
  const match = /^github:([^/]+)\/(.+)$/.exec(key);
  return match ? { owner: match[1]!, repo: match[2]! } : null;
}
