import { Cpu, KeyRound, Lock, Puzzle, Search, Wrench } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Badge } from "@/components/ui/badge.js";
import { Card, CardContent } from "@/components/ui/card.js";
import { text } from "@/components/world/editor-helpers.js";
import type { ResolvedSlot } from "@/hooks/use-slot-config.js";
import { formatSlotLabel } from "@/hooks/use-slot-config.js";
import type { UseRuntimeBindingsResult } from "@/hooks/use-runtime-bindings.js";
import {
  recommendationReason,
  textValue,
  type PluginGroup,
  type PluginPack,
} from "@/lib/session-plugin-selection.js";
import type * as api from "@/services/api.js";
import { CollapsibleCardHeader } from "./collapsible-card-header.js";
import { ExecutionFlowPreview } from "./execution-flow-preview.js";
import { WorldDataPreflightPanel } from "./world-data-preflight-panel.js";
import type { PrepSectionStatus } from "./types.js";

interface PluginSelectionCardProps {
  world: api.WorldRecord;
  packages: api.PackageSummary[];
  selectedPluginIds: string[];
  selectedPluginIdSet: ReadonlySet<string>;
  selectedPackages: api.PackageSummary[];
  expanded: boolean;
  onToggleExpanded: () => void;
  pluginPacks: PluginPack[];
  activePluginPack: PluginPack | null;
  activePluginTags: ReadonlySet<string>;
  availablePluginTags: string[];
  pluginSearch: string;
  onPluginSearchChange: (value: string) => void;
  onTogglePluginTag: (tag: string) => void;
  onApplyPack: (packId: string) => void;
  pluginGroups: PluginGroup[];
  corePluginIds: ReadonlySet<string>;
  lockedPluginIds: ReadonlySet<string>;
  bindingState: UseRuntimeBindingsResult;
  resolvedSlots: ResolvedSlot[];
  resolveDeclaredSlot: (slotId: string) => ResolvedSlot | null;
  isMissingDeclaredSlot: (slotId: string) => boolean;
  onTogglePlugin: (name: string) => void;
  worldDataPreflight: api.WorldDataPreflightResponse | null;
  worldDataPreflightStatus: PrepSectionStatus;
  worldDataPreflightError: string | null;
  onRetryWorldDataPreflight: () => void;
  flowData: api.PluginFlowResponse | null;
  selectedFlowSteps: api.PluginFlowStep[];
}

interface PluginPackageRowProps {
  pkg: api.PackageSummary;
  world: api.WorldRecord;
  activePluginPack: PluginPack | null;
  selectedPluginIdSet: ReadonlySet<string>;
  corePluginIds: ReadonlySet<string>;
  lockedPluginIds: ReadonlySet<string>;
  bindingState: UseRuntimeBindingsResult;
  resolvedSlots: ResolvedSlot[];
  resolveDeclaredSlot: (slotId: string) => ResolvedSlot | null;
  isMissingDeclaredSlot: (slotId: string) => boolean;
  onTogglePlugin: (name: string) => void;
}

function PluginPackSelector({
  pluginPacks,
  activePluginPack,
  onApplyPack,
}: {
  pluginPacks: PluginPack[];
  activePluginPack: PluginPack | null;
  onApplyPack: (packId: string) => void;
}) {
  const { t, i18n } = useTranslation();

  if (pluginPacks.length === 0) return null;

  return (
    <div className="grid grid-cols-1 sm:grid-cols-3 gap-2 mb-3">
      {pluginPacks.map((pack) => {
        const isActive = activePluginPack?.id === pack.id;
        return (
          <button
            key={pack.id}
            type="button"
            className={`border px-3 py-2 text-left transition-colors ${
              isActive
                ? "border-primary/50 bg-primary/10"
                : "border-border bg-muted/20 hover:bg-muted/40"
            }`}
            onClick={() => onApplyPack(pack.id)}
          >
            <div className="flex items-center justify-between gap-2">
              <span className="text-xs font-semibold truncate">
                {pack.labelKey
                  ? t(
                      pack.labelKey,
                      textValue(pack.label, i18n.language) || pack.id,
                    )
                  : textValue(pack.label, i18n.language) || pack.id}
              </span>
              <Badge
                variant={isActive ? "secondary" : "outline"}
                className="text-[9px] shrink-0"
              >
                {pack.plugins.length}
              </Badge>
            </div>
            {pack.description && (
              <p className="mt-1 text-[10px] text-muted-foreground line-clamp-2">
                {pack.descriptionKey
                  ? t(
                      pack.descriptionKey,
                      textValue(pack.description, i18n.language),
                    )
                  : textValue(pack.description, i18n.language)}
              </p>
            )}
          </button>
        );
      })}
    </div>
  );
}

function PluginFilterBar({
  pluginSearch,
  onPluginSearchChange,
  availablePluginTags,
  activePluginTags,
  onTogglePluginTag,
}: {
  pluginSearch: string;
  onPluginSearchChange: (value: string) => void;
  availablePluginTags: string[];
  activePluginTags: ReadonlySet<string>;
  onTogglePluginTag: (tag: string) => void;
}) {
  const { t } = useTranslation();

  return (
    <div className="flex flex-col gap-2 mb-3">
      <div className="flex items-center gap-2 border border-border bg-background px-2 py-1.5">
        <Search className="w-3.5 h-3.5 text-muted-foreground shrink-0" />
        <input
          value={pluginSearch}
          onChange={(event) => onPluginSearchChange(event.target.value)}
          className="min-w-0 flex-1 bg-transparent text-xs outline-none placeholder:text-muted-foreground"
          placeholder={t(
            "session.searchPlugins",
            "Search plugins, tags, capabilities",
          )}
        />
      </div>
      {availablePluginTags.length > 0 && (
        <div className="flex flex-wrap gap-1.5">
          {availablePluginTags.map((tag) => (
            <button
              key={tag}
              type="button"
              className={`border px-2 py-0.5 text-[10px] transition-colors ${
                activePluginTags.has(tag)
                  ? "border-primary/50 bg-primary/10 text-foreground"
                  : "border-border text-muted-foreground hover:text-foreground"
              }`}
              onClick={() => onTogglePluginTag(tag)}
            >
              {tag}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function PluginPackageRow({
  pkg,
  world,
  activePluginPack,
  selectedPluginIdSet,
  corePluginIds,
  lockedPluginIds,
  bindingState,
  resolvedSlots,
  resolveDeclaredSlot,
  isMissingDeclaredSlot,
  onTogglePlugin,
}: PluginPackageRowProps) {
  const { t, i18n } = useTranslation();
  const displayName = text(pkg.displayName) || pkg.name;
  const description = text(pkg.description);
  const isSelected = selectedPluginIdSet.has(pkg.name);
  const isLocked = lockedPluginIds.has(pkg.name);
  const isCore = corePluginIds.has(pkg.name);
  const reason = recommendationReason(pkg, world, activePluginPack, {
    locale: i18n.language,
    requiredByWorld: t(
      "session.recommendationReasons.requiredByWorld",
      "Required by world",
    ),
    packOptional: t(
      "session.recommendationReasons.packOptional",
      "Pack optional",
    ),
    recommendedByWorld: t(
      "session.recommendationReasons.recommendedByWorld",
      "Recommended by world",
    ),
  });
  const runtimes = pkg.runtimes ?? [];
  const tools = pkg.tools ?? [];
  const pluginBindings = bindingState.entries.filter(
    (entry) => entry.pluginId === pkg.name,
  );
  const primaryBinding = pluginBindings[0];
  const hasAgentRuntime = pluginBindings.length > 0;
  const providerSlotSetting = pkg.userSettings?.find(
    (spec) => spec.key === "modelPresetId",
  );
  const providerSlotName =
    typeof providerSlotSetting?.default === "string"
      ? providerSlotSetting.default
      : undefined;
  const providerSlotMissing = providerSlotName
    ? isMissingDeclaredSlot(providerSlotName)
    : false;
  const hasMissingRuntimeSlot = pluginBindings.some((binding) =>
    isMissingDeclaredSlot(binding.defaultSlot),
  );

  return (
    <div
      className={`border px-3 py-2.5 transition-colors ${
        isSelected
          ? "border-primary/40 bg-primary/5"
          : "border-border bg-muted/20 opacity-60"
      }`}
    >
      <div className="flex flex-wrap items-center gap-x-2 gap-y-1.5">
        <button
          type="button"
          role="switch"
          aria-checked={isSelected}
          disabled={isLocked}
          title={isLocked ? t("plugin.locked") : undefined}
          className={`relative inline-flex h-4 w-7 shrink-0 items-center rounded-full border-2 border-transparent transition-colors ${
            isSelected ? "bg-primary" : "bg-input"
          } ${isLocked ? "opacity-50 cursor-not-allowed" : "cursor-pointer"}`}
          onClick={() => !isLocked && onTogglePlugin(pkg.name)}
        >
          <span
            className={`pointer-events-none inline-block h-3 w-3 rounded-full bg-background shadow-sm transition ${
              isSelected ? "translate-x-3" : "translate-x-0"
            }`}
          />
        </button>

        <span className="text-xs font-medium truncate flex-1 min-w-0">
          {displayName}
        </span>
        {isCore && (
          <span
            title={t("plugin.locked")}
            className="inline-flex items-center gap-0.5 text-[9px] text-muted-foreground/70 shrink-0"
          >
            <Lock className="w-3 h-3" />
            <span className="hidden sm:inline">{t("plugin.core", "core")}</span>
          </span>
        )}
        {runtimes[0] && (
          <Badge variant="outline" className="text-[9px] shrink-0">
            P{runtimes[0].priority}
          </Badge>
        )}
        {runtimes[0]?.kind && (
          <Badge variant="secondary" className="text-[9px] shrink-0">
            {runtimes[0].kind === "agent" ? "LLM" : "Fn"}
          </Badge>
        )}
        {tools.length > 0 && (
          <span className="flex items-center gap-0.5 text-[9px] text-muted-foreground shrink-0">
            <Wrench className="w-2.5 h-2.5" />
            {tools.length}
          </span>
        )}
        {hasAgentRuntime &&
          isSelected &&
          primaryBinding &&
          pluginBindings.length === 1 &&
          !hasMissingRuntimeSlot &&
          resolvedSlots.length > 1 && (
            <select
              value={primaryBinding.slotName}
              onChange={(event) =>
                bindingState.setBinding(
                  primaryBinding.qualifiedId,
                  event.target.value,
                )
              }
              className="min-w-[100px] flex-shrink text-[11px] bg-background border border-border rounded px-2 py-1 max-w-[240px]"
              aria-label={t(
                "plugin.modelBindingAria",
                "Which model slot this plugin's runtime will use. Leave at default unless you have a reason to override.",
              )}
              title={t(
                "plugin.modelBindingAria",
                "Which model slot this plugin's runtime will use. Leave at default unless you have a reason to override.",
              )}
            >
              <option value="">
                {(() => {
                  const declared = primaryBinding.defaultSlot;
                  const defaultSlot = resolveDeclaredSlot(declared);
                  const label = formatSlotLabel(defaultSlot);
                  if (label) {
                    return t("plugin.useRuntimeDefaultWith", {
                      slot: declared,
                      value: label,
                      defaultValue: `Runtime default: ${declared} (${label})`,
                    });
                  }
                  return t("plugin.useRuntimeDefault", {
                    slot: declared,
                    defaultValue: `Runtime default: ${declared}`,
                  });
                })()}
              </option>
              {resolvedSlots.map((slot) => (
                <option key={slot.slotId} value={slot.slotId}>
                  {slot.slotId}
                  {slot.serverModel ? ` · ${slot.serverModel}` : ""}
                </option>
              ))}
            </select>
          )}
      </div>
      {description && (
        <p className="text-[11px] text-muted-foreground mt-1.5 ml-9 line-clamp-2">
          {description}
        </p>
      )}
      <div className="mt-1.5 ml-9 flex flex-wrap gap-1">
        {reason && (
          <Badge variant="secondary" className="text-[9px] px-1.5 py-0 h-4">
            {reason}
          </Badge>
        )}
        {(pkg.tags ?? []).slice(0, 4).map((tag) => (
          <Badge
            key={tag}
            variant="outline"
            className="text-[9px] px-1.5 py-0 h-4 text-muted-foreground"
          >
            {tag}
          </Badge>
        ))}
      </div>
      {isSelected && providerSlotName && (
        <div className="mt-2.5 ml-9 flex items-center gap-2.5 text-[11px] text-muted-foreground">
          <KeyRound className="w-3 h-3 shrink-0" />
          <span className="font-medium shrink-0">
            {t("plugin.providerSlot", "provider slot")}
          </span>
          <Badge
            variant={providerSlotMissing ? "destructive" : "outline"}
            className="text-[10px] px-1.5 py-0.5 h-5 shrink-0"
            title={
              providerSlotMissing
                ? t("plugin.providerSlotMissingTitle", {
                    slot: providerSlotName,
                    plugin: pkg.name,
                    defaultValue:
                      "Add [covel.{{slot}}] to llm.toml, or change {{plugin}}.modelPresetId in Settings > Plugins.",
                  })
                : undefined
            }
          >
            {providerSlotMissing
              ? t("plugin.slotMissingShort", {
                  slot: providerSlotName,
                  defaultValue: "missing [covel.{{slot}}]",
                })
              : `[covel.${providerSlotName}]`}
          </Badge>
          <span className="truncate">
            {providerSlotMissing
              ? t("plugin.providerSlotMissingDetail", {
                  slot: providerSlotName,
                  plugin: pkg.name,
                  defaultValue:
                    "Configure [covel.{{slot}}] in llm.toml, or change modelPresetId in Settings > Plugins > {{plugin}}.",
                })
              : t("plugin.providerSlotSettingDetail", {
                  plugin: pkg.name,
                  defaultValue:
                    "from plugin setting modelPresetId; edit in Settings > Plugins > {{plugin}}",
                })}
          </span>
        </div>
      )}
      {isSelected &&
        pluginBindings.length > 0 &&
        (pluginBindings.length > 1 || hasMissingRuntimeSlot) && (
          <div className="mt-2.5 ml-9 space-y-1.5">
            {pluginBindings.map((binding) => {
              const declaredSlot = binding.defaultSlot;
              const configuredDefault = resolveDeclaredSlot(declaredSlot);
              const selectedSlot = binding.slotName
                ? resolvedSlots.find((slot) => slot.slotId === binding.slotName)
                : configuredDefault;
              const missingDefault = isMissingDeclaredSlot(declaredSlot);
              const showPicker =
                pluginBindings.length > 1 ||
                missingDefault ||
                resolvedSlots.length > 1;
              return (
                <div
                  key={binding.qualifiedId}
                  className="flex flex-wrap items-center gap-x-2.5 gap-y-1 text-[11px] text-muted-foreground min-w-0"
                >
                  <Cpu className="w-3 h-3 shrink-0" />
                  <span
                    className="font-mono truncate min-w-0 max-w-[240px]"
                    title={binding.qualifiedId}
                  >
                    {binding.qualifiedId}
                  </span>
                  <Badge
                    variant={missingDefault ? "destructive" : "outline"}
                    className="text-[10px] px-1.5 py-0.5 h-5 shrink-0"
                    title={
                      missingDefault
                        ? t("plugin.slotMissingTitle", {
                            slot: declaredSlot,
                            defaultValue: "Add [covel.{{slot}}] to llm.toml",
                          })
                        : undefined
                    }
                  >
                    {missingDefault
                      ? t("plugin.slotMissingShort", {
                          slot: declaredSlot,
                          defaultValue: "missing [covel.{{slot}}]",
                        })
                      : `default: ${declaredSlot}`}
                  </Badge>
                  {missingDefault && (
                    <code className="text-[10px] text-muted-foreground/80 bg-muted px-1.5 py-0.5 rounded shrink-0">
                      [covel.{declaredSlot}]
                    </code>
                  )}
                  {showPicker ? (
                    <select
                      value={binding.slotName}
                      onChange={(event) =>
                        bindingState.setBinding(
                          binding.qualifiedId,
                          event.target.value,
                        )
                      }
                      className="ml-auto min-w-[120px] flex-shrink text-[11px] bg-background border border-border rounded px-2 py-1 max-w-[280px]"
                      aria-label={t(
                        "plugin.modelBindingAria",
                        "Which model slot this plugin's runtime will use. Leave at default unless you have a reason to override.",
                      )}
                    >
                      <option value="">
                        {configuredDefault
                          ? configuredDefault.serverModel
                            ? t("plugin.runtimeDefaultSummaryWithModel", {
                                slot: declaredSlot,
                                model: configuredDefault.serverModel,
                                defaultValue:
                                  "runtime default · {{slot}} · {{model}}",
                              })
                            : t("plugin.runtimeDefaultSummary", {
                                slot: declaredSlot,
                                defaultValue: "runtime default · {{slot}}",
                              })
                          : t("plugin.runtimeDefaultMissing", {
                              slot: declaredSlot,
                              defaultValue:
                                "runtime default · {{slot}} (missing)",
                            })}
                      </option>
                      {resolvedSlots.map((slot) => (
                        <option key={slot.slotId} value={slot.slotId}>
                          {slot.slotId}
                          {slot.serverModel ? ` · ${slot.serverModel}` : ""}
                        </option>
                      ))}
                    </select>
                  ) : selectedSlot ? (
                    <span
                      className="ml-auto truncate text-[11px]"
                      title={
                        formatSlotLabel(selectedSlot) ?? selectedSlot.slotId
                      }
                    >
                      {formatSlotLabel(selectedSlot) ?? selectedSlot.slotId}
                    </span>
                  ) : null}
                </div>
              );
            })}
          </div>
        )}
    </div>
  );
}

export function PluginSelectionCard({
  world,
  packages,
  selectedPluginIds,
  selectedPluginIdSet,
  selectedPackages,
  expanded,
  onToggleExpanded,
  pluginPacks,
  activePluginPack,
  activePluginTags,
  availablePluginTags,
  pluginSearch,
  onPluginSearchChange,
  onTogglePluginTag,
  onApplyPack,
  pluginGroups,
  corePluginIds,
  lockedPluginIds,
  bindingState,
  resolvedSlots,
  resolveDeclaredSlot,
  isMissingDeclaredSlot,
  onTogglePlugin,
  worldDataPreflight,
  worldDataPreflightStatus,
  worldDataPreflightError,
  onRetryWorldDataPreflight,
  flowData,
  selectedFlowSteps,
}: PluginSelectionCardProps) {
  const { t } = useTranslation();
  const totalRuntimes = selectedPackages.reduce(
    (sum, pkg) => sum + (pkg.runtimes?.length ?? 0),
    0,
  );

  return (
    <Card>
      <CollapsibleCardHeader
        expanded={expanded}
        onToggle={onToggleExpanded}
        summary={`${selectedPluginIds.length}/${packages.length} ${t("session.pluginsSelected", "plugins selected")} · ${totalRuntimes} runtimes`}
      >
        <Puzzle className="w-4 h-4" />
        {t("session.plugins", "Plugins & Runtimes")}
        <Badge variant="secondary" className="text-[10px] ml-1">
          {selectedPluginIds.length}/{packages.length}
        </Badge>
      </CollapsibleCardHeader>
      {expanded && (
        <CardContent className="space-y-4 px-4 pb-4">
          <div className="space-y-1.5">
            <PluginPackSelector
              pluginPacks={pluginPacks}
              activePluginPack={activePluginPack}
              onApplyPack={onApplyPack}
            />
            <PluginFilterBar
              pluginSearch={pluginSearch}
              onPluginSearchChange={onPluginSearchChange}
              availablePluginTags={availablePluginTags}
              activePluginTags={activePluginTags}
              onTogglePluginTag={onTogglePluginTag}
            />

            {pluginGroups.map((group) => (
              <div key={group.id} className="space-y-1.5">
                <div className="flex items-center justify-between gap-3 pt-2">
                  <h4 className="text-[10px] font-semibold text-muted-foreground uppercase tracking-widest">
                    {group.label}
                  </h4>
                  <span className="text-[10px] text-muted-foreground">
                    {group.packages.length}
                  </span>
                </div>
                {group.packages.map((pkg) => (
                  <PluginPackageRow
                    key={pkg.name}
                    pkg={pkg}
                    world={world}
                    activePluginPack={activePluginPack}
                    selectedPluginIdSet={selectedPluginIdSet}
                    corePluginIds={corePluginIds}
                    lockedPluginIds={lockedPluginIds}
                    bindingState={bindingState}
                    resolvedSlots={resolvedSlots}
                    resolveDeclaredSlot={resolveDeclaredSlot}
                    isMissingDeclaredSlot={isMissingDeclaredSlot}
                    onTogglePlugin={onTogglePlugin}
                  />
                ))}
              </div>
            ))}
          </div>

          <WorldDataPreflightPanel
            result={worldDataPreflight}
            status={worldDataPreflightStatus}
            error={worldDataPreflightError}
            onRetry={onRetryWorldDataPreflight}
          />

          <ExecutionFlowPreview
            flowData={flowData}
            selectedFlowSteps={selectedFlowSteps}
            bindingState={bindingState}
          />
        </CardContent>
      )}
    </Card>
  );
}
