"use client";

import { useState, useEffect, useRef } from "react";
import { z } from "zod";
import { Trash2, Plus } from "lucide-react";
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { Button } from "@/components/ui/button";
import { TagInput } from "./tag-input";
import { BlockTimeline, BLOCK_COLORS, minsToTime } from "./block-timeline";
import { SeriesPicker } from "./series-picker";
import { FilterPreview } from "./filter-preview";
import { useCollections, useSeries, useGenres } from "@/hooks/use-library";
import type {
  AccessMode,
  ChannelResponse,
  LogoPosition,
  ProgrammingBlock,
  BlockContent,
  FillStrategy,
  ContentType,
  MediaFilter,
  ProviderInfo,
  RecyclePolicy,
} from "@/lib/types";

// ---------------------------------------------------------------------------
// Webhook preset templates (frontend-only, zero backend changes needed)
// ---------------------------------------------------------------------------

const WEBHOOK_PRESETS = {
  discord: `{
  "embeds": [{
    "title": "📺 {{event}}",
    "description": "{{#if data.item.title}}Now playing: **{{data.item.title}}**{{else}}No signal{{/if}}",
    "color": 3447003,
    "timestamp": "{{timestamp}}"
  }]
}`,
  slack: `{
  "text": "📺 *{{event}}*{{#if data.item.title}} — {{data.item.title}}{{/if}}"
}`,
} as const;

type WebhookFormat = "default" | "discord" | "slack" | "custom";

// ---------------------------------------------------------------------------
// Zod schemas
// ---------------------------------------------------------------------------

const mediaFilterSchema = z.object({
  content_type: z.enum(["movie", "episode", "short"]).nullable().optional(),
  genres: z.array(z.string()),
  decade: z
    .number()
    .int()
    .min(1900, "Decade must be ≥ 1900")
    .max(2099, "Decade must be ≤ 2090")
    .nullable()
    .optional(),
  tags: z.array(z.string()),
  min_duration_secs: z.number().min(0, "Must be ≥ 0").nullable().optional(),
  max_duration_secs: z.number().min(0, "Must be ≥ 0").nullable().optional(),
  collections: z.array(z.string()),
  series_names: z.array(z.string()),
  search_term: z.string().nullable().optional(),
});

const accessModeSchema = z.enum(["public", "password_protected", "account_required", "owner_only"]);

const blockSchema = z.object({
  id: z.string(),
  name: z.string().min(1, "Block name is required"),
  start_time: z.string(),
  duration_mins: z.number().int().min(1, "Must be at least 1 minute"),
  content: z.discriminatedUnion("type", [
    z.object({
      type: z.literal("algorithmic"),
      filter: mediaFilterSchema,
      strategy: z.enum(["best_fit", "sequential", "random"]),
      provider_id: z.string().optional(),
    }),
    z.object({
      type: z.literal("manual"),
      items: z.array(z.string()),
      provider_id: z.string().optional(),
    }),
  ]),
  loop_on_finish: z.boolean().optional(),
  ignore_recycle_policy: z.boolean().optional(),
  access_mode: accessModeSchema.optional(),
  access_password: z.string().optional(),
});

const channelFormSchema = z.object({
  name: z.string().min(1, "Name is required"),
  timezone: z.string().min(1, "Timezone is required"),
  description: z.string().optional(),
  blocks: z.array(blockSchema),
  recycle_policy: z.object({
    cooldown_days: z.number().int().min(0).nullable().optional(),
    cooldown_generations: z.number().int().min(0).nullable().optional(),
    min_available_ratio: z.number().min(0, "Must be ≥ 0").max(1, "Must be ≤ 1"),
  }),
  auto_schedule: z.boolean(),
  access_mode: accessModeSchema.optional(),
  access_password: z.string().optional(),
});

type FieldErrors = Record<string, string | undefined>;

function extractErrors(err: z.ZodError): FieldErrors {
  const map: FieldErrors = {};
  for (const issue of err.issues) {
    const key = issue.path.join(".");
    if (!map[key]) map[key] = issue.message;
  }
  return map;
}

// ---------------------------------------------------------------------------
// Field wrapper
// ---------------------------------------------------------------------------

function Field({
  label,
  hint,
  error,
  children,
}: {
  label: string;
  hint?: string;
  error?: string;
  children: React.ReactNode;
}) {
  return (
    <div className="space-y-1.5">
      <label className="block text-xs font-medium text-zinc-400">{label}</label>
      {children}
      {error ? (
        <p className="text-[11px] text-red-400">{error}</p>
      ) : hint ? (
        <p className="text-[11px] text-zinc-600">{hint}</p>
      ) : null}
    </div>
  );
}

function TextInput({
  value,
  onChange,
  placeholder,
  required,
  error,
}: {
  value: string;
  onChange: (v: string) => void;
  placeholder?: string;
  required?: boolean;
  error?: boolean;
}) {
  return (
    <input
      required={required}
      value={value}
      onChange={(e) => onChange(e.target.value)}
      placeholder={placeholder}
      className={`w-full rounded-md border bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:outline-none ${error ? "border-red-500 focus:border-red-400" : "border-zinc-700 focus:border-zinc-500"}`}
    />
  );
}

function NumberInput({
  value,
  onChange,
  min,
  max,
  step,
  placeholder,
  error,
}: {
  value: number | "";
  onChange: (v: number | "") => void;
  min?: number;
  max?: number;
  step?: number | "any";
  placeholder?: string;
  error?: boolean;
}) {
  return (
    <input
      type="number"
      min={min}
      max={max}
      step={step}
      value={value}
      placeholder={placeholder}
      onChange={(e) => onChange(e.target.value === "" ? "" : Number(e.target.value))}
      className={`w-full rounded-md border bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:outline-none ${error ? "border-red-500 focus:border-red-400" : "border-zinc-700 focus:border-zinc-500"}`}
    />
  );
}

function NativeSelect({
  value,
  onChange,
  children,
}: {
  value: string;
  onChange: (v: string) => void;
  children: React.ReactNode;
}) {
  return (
    <select
      value={value}
      onChange={(e) => onChange(e.target.value)}
      className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 focus:border-zinc-500 focus:outline-none"
    >
      {children}
    </select>
  );
}

// ---------------------------------------------------------------------------
// Defaults
// ---------------------------------------------------------------------------

function defaultFilter(): MediaFilter {
  return {
    content_type: null,
    genres: [],
    decade: null,
    tags: [],
    min_duration_secs: null,
    max_duration_secs: null,
    collections: [],
    series_names: [],
    search_term: null,
  };
}

function defaultBlock(startMins = 20 * 60, durationMins = 60): ProgrammingBlock {
  return {
    id: crypto.randomUUID(),
    name: "",
    start_time: minsToTime(startMins),
    duration_mins: durationMins,
    content: { type: "algorithmic", filter: defaultFilter(), strategy: "random" },
    loop_on_finish: true,
    ignore_recycle_policy: false,
    access_mode: "public",
  };
}

// ---------------------------------------------------------------------------
// AlgorithmicFilterEditor — filter section with live library data
// ---------------------------------------------------------------------------

interface AlgorithmicFilterEditorProps {
  content: Extract<BlockContent, { type: "algorithmic" }>;
  pfx: string;
  errors: FieldErrors;
  setFilter: (patch: Partial<MediaFilter>) => void;
  setStrategy: (strategy: FillStrategy) => void;
  setProviderId: (id: string) => void;
  providers: ProviderInfo[];
}

function AlgorithmicFilterEditor({
  content,
  pfx,
  errors,
  setFilter,
  setStrategy,
  setProviderId,
  providers,
}: AlgorithmicFilterEditorProps) {
  const [showGenres, setShowGenres] = useState(false);

  const providerId = content.provider_id ?? "";
  const capabilities = providers.find((p) => p.id === providerId)?.capabilities
    ?? providers[0]?.capabilities;

  const { data: collections, isLoading: loadingCollections } = useCollections(providerId || undefined);
  const { data: series, isLoading: loadingSeries } = useSeries(undefined, {
    enabled: capabilities?.series !== false,
    provider: providerId || undefined,
  });
  const { data: genreOptions } = useGenres(content.filter.content_type ?? undefined, {
    enabled: capabilities?.genres !== false,
    provider: providerId || undefined,
  });

  const isEpisode = content.filter.content_type === "episode";
  const collectionLabel =
    capabilities?.collections && !capabilities?.series && !capabilities?.genres
      ? "Directory"
      : "Library";

  return (
    <div className="space-y-3 rounded-md border border-zinc-700/50 bg-zinc-800 p-3">
      <p className="text-[11px] font-medium uppercase tracking-wider text-zinc-500">Filter</p>

      {providers.length > 1 && (
        <Field label="Provider">
          <NativeSelect value={providerId} onChange={(v) => setProviderId(v)}>
            {providers.map((p) => (
              <option key={p.id} value={p.id}>{p.id}</option>
            ))}
          </NativeSelect>
        </Field>
      )}

      <div className="grid grid-cols-2 gap-3">
        <Field label="Media type">
          <NativeSelect
            value={content.filter.content_type ?? ""}
            onChange={(v) =>
              setFilter({
                content_type: v === "" ? null : (v as ContentType),
                // clear series names if switching away from episode
                series_names: v !== "episode" ? [] : content.filter.series_names,
              })
            }
          >
            <option value="">Any</option>
            <option value="movie">Movie</option>
            <option value="episode">Episode</option>
            <option value="short">Short</option>
          </NativeSelect>
        </Field>
        <Field label="Strategy">
          <NativeSelect
            value={content.strategy}
            onChange={(v) => setStrategy(v as FillStrategy)}
          >
            <option value="random">Random</option>
            <option value="best_fit">Best fit</option>
            <option value="sequential">Sequential</option>
          </NativeSelect>
        </Field>
      </div>

      {/* Series — only meaningful for episodes when provider supports it */}
      {isEpisode && capabilities?.series !== false && (
        <Field
          label="Series"
          hint={
            content.strategy === "sequential"
              ? "Episodes will play in chronological order"
              : "Filter to specific shows, or leave empty for all"
          }
        >
          <SeriesPicker
            values={content.filter.series_names ?? []}
            onChange={(v) => setFilter({ series_names: v })}
            series={series ?? []}
            isLoading={loadingSeries}
          />
        </Field>
      )}

      {/* Library/Directory — real collection names when the provider supports it */}
      <Field
        label={collectionLabel}
        hint={
          loadingCollections
            ? `Loading ${collectionLabel.toLowerCase()}s…`
            : collections
              ? `Scope this block to one ${collectionLabel.toLowerCase()}`
              : `Enter a provider ${collectionLabel.toLowerCase()} ID`
        }
      >
        {collections && collections.length > 0 ? (
          <NativeSelect
            value={content.filter.collections[0] ?? ""}
            onChange={(v) => setFilter({ collections: v ? [v] : [] })}
          >
            <option value="">All libraries</option>
            {collections.map((c) => (
              <option key={c.id} value={c.id}>
                {c.name}
                {c.collection_type ? ` (${c.collection_type})` : ""}
              </option>
            ))}
          </NativeSelect>
        ) : (
          <TagInput
            values={content.filter.collections}
            onChange={(v) => setFilter({ collections: v })}
            placeholder="Library ID…"
          />
        )}
      </Field>

      {/* Genres — only shown when provider supports it */}
      {capabilities?.genres !== false && (
      <Field label="Genres" hint="Press Enter or comma to add">
        <TagInput
          values={content.filter.genres}
          onChange={(v) => setFilter({ genres: v })}
          placeholder="Comedy, Animation…"
        />
        {genreOptions && genreOptions.length > 0 && (
          <div className="mt-1.5">
            <button
              type="button"
              onClick={() => setShowGenres((s) => !s)}
              className="text-[11px] text-zinc-600 hover:text-zinc-400"
            >
              {showGenres ? "Hide" : "Browse"} available genres
            </button>
            {showGenres && (
              <div className="mt-1.5 flex flex-wrap gap-1">
                {genreOptions
                  .filter((g) => !content.filter.genres.includes(g))
                  .map((g) => (
                    <button
                      key={g}
                      type="button"
                      onClick={() => setFilter({ genres: [...content.filter.genres, g] })}
                      className="rounded px-1.5 py-0.5 text-[11px] bg-zinc-700/50 text-zinc-400 hover:bg-zinc-700 hover:text-zinc-200"
                    >
                      + {g}
                    </button>
                  ))}
              </div>
            )}
          </div>
        )}
      </Field>
      )}

      <Field label="Tags" hint="Press Enter or comma to add">
        <TagInput
          values={content.filter.tags}
          onChange={(v) => setFilter({ tags: v })}
          placeholder="classic, family…"
        />
      </Field>

      <div className="grid grid-cols-3 gap-3">
        <Field label="Decade" hint="e.g. 1990" error={errors[`${pfx}.content.filter.decade`]}>
          <NumberInput
            value={content.filter.decade ?? ""}
            onChange={(v) => setFilter({ decade: v === "" ? null : (v as number) })}
            placeholder="1990"
            error={!!errors[`${pfx}.content.filter.decade`]}
          />
        </Field>
        <Field label="Min duration (min)" error={errors[`${pfx}.content.filter.min_duration_secs`]}>
          <NumberInput
            value={content.filter.min_duration_secs != null ? Math.round(content.filter.min_duration_secs / 60) : ""}
            onChange={(v) =>
              setFilter({ min_duration_secs: v === "" ? null : (v as number) * 60 })
            }
            placeholder="30"
            error={!!errors[`${pfx}.content.filter.min_duration_secs`]}
          />
        </Field>
        <Field label="Max duration (min)" error={errors[`${pfx}.content.filter.max_duration_secs`]}>
          <NumberInput
            value={content.filter.max_duration_secs != null ? Math.round(content.filter.max_duration_secs / 60) : ""}
            onChange={(v) =>
              setFilter({ max_duration_secs: v === "" ? null : (v as number) * 60 })
            }
            placeholder="120"
            error={!!errors[`${pfx}.content.filter.max_duration_secs`]}
          />
        </Field>
      </div>

      {/* Preview — snapshot of current filter+strategy, only fetches on explicit click */}
      <FilterPreview filter={content.filter} strategy={content.strategy} provider={providerId || undefined} />
    </div>
  );
}

// ---------------------------------------------------------------------------
// BlockEditor (detail form for a single block)
// ---------------------------------------------------------------------------

interface BlockEditorProps {
  block: ProgrammingBlock;
  index: number;
  errors: FieldErrors;
  onChange: (block: ProgrammingBlock) => void;
  providers: ProviderInfo[];
}

function BlockEditor({ block, index, errors, onChange, providers }: BlockEditorProps) {
  const setField = <K extends keyof ProgrammingBlock>(key: K, value: ProgrammingBlock[K]) =>
    onChange({ ...block, [key]: value });

  const content = block.content;
  const pfx = `blocks.${index}`;

  const setContentType = (type: "algorithmic" | "manual") => {
    const pid = content.provider_id ?? "";
    onChange({
      ...block,
      content:
        type === "algorithmic"
          ? { type: "algorithmic", filter: defaultFilter(), strategy: "random", provider_id: pid }
          : { type: "manual", items: [], provider_id: pid },
    });
  };

  const setFilter = (patch: Partial<MediaFilter>) => {
    if (content.type !== "algorithmic") return;
    onChange({ ...block, content: { ...content, filter: { ...content.filter, ...patch } } });
  };

  const setStrategy = (strategy: FillStrategy) => {
    if (content.type !== "algorithmic") return;
    onChange({ ...block, content: { ...content, strategy } });
  };

  const setProviderId = (id: string) => {
    onChange({ ...block, content: { ...content, provider_id: id } });
  };

  return (
    <div className="space-y-3">
      <div className="grid grid-cols-2 gap-3">
        <Field label="Block name" error={errors[`${pfx}.name`]}>
          <TextInput
            value={block.name}
            onChange={(v) => setField("name", v)}
            placeholder="Evening Sitcoms"
            error={!!errors[`${pfx}.name`]}
          />
        </Field>
        <Field label="Content type">
          <NativeSelect
            value={content.type}
            onChange={(v) => setContentType(v as "algorithmic" | "manual")}
          >
            <option value="algorithmic">Algorithmic</option>
            <option value="manual">Manual</option>
          </NativeSelect>
        </Field>
      </div>

      <div className="grid grid-cols-2 gap-3">
        <Field label="Start time">
          <input
            type="time"
            value={block.start_time.slice(0, 5)}
            onChange={(e) => setField("start_time", e.target.value + ":00")}
            className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 focus:border-zinc-500 focus:outline-none"
          />
        </Field>
        <Field label="Duration (minutes)" error={errors[`${pfx}.duration_mins`]}>
          <NumberInput
            value={block.duration_mins}
            onChange={(v) => setField("duration_mins", v === "" ? 60 : v)}
            min={1}
            error={!!errors[`${pfx}.duration_mins`]}
          />
        </Field>
      </div>

      {content.type === "algorithmic" && (
        <>
          <AlgorithmicFilterEditor
            content={content}
            pfx={pfx}
            errors={errors}
            setFilter={setFilter}
            setStrategy={setStrategy}
            setProviderId={setProviderId}
            providers={providers}
          />

          {content.strategy === "sequential" && (
            <div className="space-y-2 rounded-md border border-zinc-700/50 bg-zinc-800 p-3">
              <p className="text-[11px] font-medium uppercase tracking-wider text-zinc-500">
                Sequential options
              </p>
              <label className="flex items-center gap-2 cursor-pointer">
                <input
                  type="checkbox"
                  checked={block.loop_on_finish ?? true}
                  onChange={(e) => onChange({ ...block, loop_on_finish: e.target.checked })}
                  className="accent-zinc-400"
                />
                <span className="text-sm text-zinc-300">Loop series</span>
                <span className="text-[11px] text-zinc-600">
                  Restart from episode 1 after the final episode
                </span>
              </label>
              <label className="flex items-center gap-2 cursor-pointer">
                <input
                  type="checkbox"
                  checked={block.ignore_recycle_policy ?? false}
                  onChange={(e) => onChange({ ...block, ignore_recycle_policy: e.target.checked })}
                  className="accent-zinc-400"
                />
                <span className="text-sm text-zinc-300">Independent scheduling</span>
                <span className="text-[11px] text-zinc-600">
                  Play episodes in order even if they aired in another block today
                </span>
              </label>
            </div>
          )}
        </>
      )}

      {content.type === "manual" && (
        <div className="space-y-2 rounded-md border border-zinc-700/50 bg-zinc-800 p-3">
          <p className="text-[11px] font-medium uppercase tracking-wider text-zinc-500">Item IDs</p>
          <textarea
            rows={3}
            value={content.items.join("\n")}
            onChange={(e) =>
              onChange({
                ...block,
                content: {
                  type: "manual",
                  items: e.target.value.split("\n").map((s) => s.trim()).filter(Boolean),
                },
              })
            }
            placeholder={"abc123\ndef456\nghi789"}
            className="w-full resize-none rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 font-mono text-xs text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
          />
          <p className="text-[11px] text-zinc-600">One Jellyfin item ID per line, played in order.</p>
        </div>
      )}

      {/* Block-level access control */}
      <div className="space-y-2 rounded-md border border-zinc-700/50 bg-zinc-800 p-3">
        <p className="text-[11px] font-medium uppercase tracking-wider text-zinc-500">Block access</p>
        <NativeSelect
          value={block.access_mode ?? "public"}
          onChange={(v) => onChange({ ...block, access_mode: v as AccessMode })}
        >
          <option value="public">Public</option>
          <option value="password_protected">Password protected</option>
          <option value="account_required">Account required</option>
          <option value="owner_only">Owner only</option>
        </NativeSelect>
        {(block.access_mode === "password_protected") && (
          <input
            type="password"
            placeholder="Block password (leave blank to keep existing)"
            value={block.access_password ?? ""}
            onChange={(e) => onChange({ ...block, access_password: e.target.value })}
            className="w-full rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
          />
        )}
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Recycle policy editor
// ---------------------------------------------------------------------------

function RecyclePolicyEditor({
  policy,
  errors,
  onChange,
}: {
  policy: RecyclePolicy;
  errors: FieldErrors;
  onChange: (policy: RecyclePolicy) => void;
}) {
  return (
    <div className="space-y-3">
      <div className="grid grid-cols-2 gap-3">
        <Field label="Cooldown (days)" hint="Don't replay within N days">
          <NumberInput
            value={policy.cooldown_days ?? ""}
            onChange={(v) => onChange({ ...policy, cooldown_days: v === "" ? null : (v as number) })}
            min={0}
            placeholder="7"
          />
        </Field>
        <Field label="Cooldown (generations)" hint="Don't replay within N schedules">
          <NumberInput
            value={policy.cooldown_generations ?? ""}
            onChange={(v) => onChange({ ...policy, cooldown_generations: v === "" ? null : (v as number) })}
            min={0}
            placeholder="3"
          />
        </Field>
      </div>
      <Field
        label="Min available ratio"
        hint="0.0–1.0 · Fraction of the pool kept selectable even if cooldown is active"
        error={errors["recycle_policy.min_available_ratio"]}
      >
        <NumberInput
          value={policy.min_available_ratio}
          onChange={(v) => onChange({ ...policy, min_available_ratio: v === "" ? 0.1 : (v as number) })}
          min={0}
          max={1}
          step={0.01}
          placeholder="0.1"
          error={!!errors["recycle_policy.min_available_ratio"]}
        />
      </Field>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Main sheet
// ---------------------------------------------------------------------------

interface EditChannelSheetProps {
  channel: ChannelResponse | null;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onSubmit: (
    id: string,
    data: {
      name: string;
      description: string;
      timezone: string;
      schedule_config: { blocks: ProgrammingBlock[] };
      recycle_policy: RecyclePolicy;
      auto_schedule: boolean;
      access_mode?: AccessMode;
      access_password?: string;
      logo?: string | null;
      logo_position?: LogoPosition;
      logo_opacity?: number;
      webhook_url?: string | null;
      webhook_poll_interval_secs?: number;
      webhook_body_template?: string | null;
      webhook_headers?: string | null;
    },
  ) => void;
  isPending: boolean;
  error?: string | null;
  providers?: ProviderInfo[];
}

export function EditChannelSheet({
  channel,
  open,
  onOpenChange,
  onSubmit,
  isPending,
  error,
  providers = [],
}: EditChannelSheetProps) {
  const [name, setName] = useState("");
  const [description, setDescription] = useState("");
  const [timezone, setTimezone] = useState("UTC");
  const [blocks, setBlocks] = useState<ProgrammingBlock[]>([]);
  const [recyclePolicy, setRecyclePolicy] = useState<RecyclePolicy>({
    cooldown_days: null,
    cooldown_generations: null,
    min_available_ratio: 0.1,
  });
  const [autoSchedule, setAutoSchedule] = useState(false);
  const [accessMode, setAccessMode] = useState<AccessMode>("public");
  const [accessPassword, setAccessPassword] = useState("");
  const [logo, setLogo] = useState<string | null>(null);
  const [logoPosition, setLogoPosition] = useState<LogoPosition>("top_right");
  const [logoOpacity, setLogoOpacity] = useState(100);
  const [webhookUrl, setWebhookUrl] = useState("");
  const [webhookPollInterval, setWebhookPollInterval] = useState<number | "">(5);
  const [webhookFormat, setWebhookFormat] = useState<WebhookFormat>("default");
  const [webhookBodyTemplate, setWebhookBodyTemplate] = useState("");
  const [webhookHeaders, setWebhookHeaders] = useState("");
  const [selectedBlockId, setSelectedBlockId] = useState<string | null>(null);
  const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
  const fileInputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (channel) {
      setName(channel.name);
      setDescription(channel.description ?? "");
      setTimezone(channel.timezone);
      setBlocks(channel.schedule_config.blocks);
      setRecyclePolicy(channel.recycle_policy);
      setAutoSchedule(channel.auto_schedule);
      setAccessMode(channel.access_mode ?? "public");
      setAccessPassword("");
      setLogo(channel.logo ?? null);
      setLogoPosition(channel.logo_position ?? "top_right");
      setLogoOpacity(Math.round((channel.logo_opacity ?? 1) * 100));
      setWebhookUrl(channel.webhook_url ?? "");
      setWebhookPollInterval(channel.webhook_poll_interval_secs ?? 5);
      const tmpl = channel.webhook_body_template ?? "";
      setWebhookBodyTemplate(tmpl);
      setWebhookHeaders(channel.webhook_headers ?? "");
      if (!tmpl) {
        setWebhookFormat("default");
      } else if (tmpl === WEBHOOK_PRESETS.discord) {
        setWebhookFormat("discord");
      } else if (tmpl === WEBHOOK_PRESETS.slack) {
        setWebhookFormat("slack");
      } else {
        setWebhookFormat("custom");
      }
      setSelectedBlockId(null);
      setFieldErrors({});
    }
  }, [channel]);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!channel) return;

    const result = channelFormSchema.safeParse({
      name, description, timezone, blocks, recycle_policy: recyclePolicy,
      auto_schedule: autoSchedule, access_mode: accessMode, access_password: accessPassword,
    });

    if (!result.success) {
      setFieldErrors(extractErrors(result.error));
      return;
    }

    setFieldErrors({});
    onSubmit(channel.id, {
      name,
      description,
      timezone,
      schedule_config: { blocks },
      recycle_policy: recyclePolicy,
      auto_schedule: autoSchedule,
      access_mode: accessMode !== "public" ? accessMode : "public",
      access_password: accessPassword || "",
      logo: logo,
      logo_position: logoPosition,
      logo_opacity: logoOpacity / 100,
      webhook_url: webhookUrl || null,
      ...(webhookUrl
        ? { webhook_poll_interval_secs: webhookPollInterval === "" ? 5 : webhookPollInterval }
        : {}),
      webhook_body_template: webhookBodyTemplate || null,
      webhook_headers: webhookHeaders || null,
    });
  };

  const addBlock = (startMins = 20 * 60, durationMins = 60) => {
    const block = defaultBlock(startMins, durationMins);
    setBlocks((prev) => [...prev, block]);
    setSelectedBlockId(block.id);
  };

  const updateBlock = (idx: number, block: ProgrammingBlock) =>
    setBlocks((prev) => prev.map((b, i) => (i === idx ? block : b)));

  const removeBlock = (idx: number) => {
    setBlocks((prev) => {
      const next = prev.filter((_, i) => i !== idx);
      if (selectedBlockId === prev[idx].id) setSelectedBlockId(null);
      return next;
    });
  };

  return (
    <Sheet open={open} onOpenChange={onOpenChange}>
      <SheetContent
        side="right"
        className="flex w-full flex-col gap-0 border-zinc-800 bg-zinc-900 p-0 text-zinc-100 sm:!max-w-[min(1100px,95vw)]"
      >
        <SheetHeader className="border-b border-zinc-800 px-6 py-4">
          <SheetTitle className="text-zinc-100">Edit channel</SheetTitle>
        </SheetHeader>

        <form onSubmit={handleSubmit} className="flex flex-1 flex-col overflow-hidden">
          <div className="flex flex-1 overflow-hidden">
            {/* Left column — basic info, logo, recycle policy, webhook */}
            <div className="w-[300px] shrink-0 overflow-y-auto border-r border-zinc-800 px-5 py-4 space-y-6">
              {/* Basic info */}
              <section className="space-y-3">
                <h3 className="text-xs font-semibold uppercase tracking-wider text-zinc-500">Basic info</h3>

                <Field label="Name" error={fieldErrors["name"]}>
                  <TextInput
                    required
                    value={name}
                    onChange={setName}
                    placeholder="90s Sitcom Network"
                    error={!!fieldErrors["name"]}
                  />
                </Field>

                <Field label="Timezone" hint="IANA timezone, e.g. America/New_York" error={fieldErrors["timezone"]}>
                  <TextInput
                    required
                    value={timezone}
                    onChange={setTimezone}
                    placeholder="UTC"
                    error={!!fieldErrors["timezone"]}
                  />
                </Field>

                <Field label="Description">
                  <textarea
                    value={description}
                    onChange={(e) => setDescription(e.target.value)}
                    rows={2}
                    placeholder="Nothing but classic sitcoms, all day"
                    className="w-full resize-none rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 text-sm text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
                  />
                </Field>

                <label className="flex items-center justify-between gap-3 cursor-pointer rounded-md border border-zinc-700 bg-zinc-800/50 px-3 py-2.5">
                  <div>
                    <p className="text-sm text-zinc-200">Auto-generate schedule</p>
                    <p className="text-[11px] text-zinc-600">Automatically regenerate when the schedule is about to expire</p>
                  </div>
                  <button
                    type="button"
                    role="switch"
                    aria-checked={autoSchedule}
                    onClick={() => setAutoSchedule((v) => !v)}
                    className={`relative inline-flex h-5 w-9 shrink-0 rounded-full border-2 border-transparent transition-colors focus:outline-none ${autoSchedule ? "bg-zinc-300" : "bg-zinc-700"}`}
                  >
                    <span
                      className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow-sm transition-transform ${autoSchedule ? "translate-x-4" : "translate-x-0"}`}
                    />
                  </button>
                </label>

                <Field label="Channel access">
                  <NativeSelect value={accessMode} onChange={(v) => { setAccessMode(v as AccessMode); setAccessPassword(""); }}>
                    <option value="public">Public</option>
                    <option value="password_protected">Password protected</option>
                    <option value="account_required">Account required</option>
                    <option value="owner_only">Owner only</option>
                  </NativeSelect>
                </Field>

                {accessMode === "password_protected" && (
                  <Field label="Channel password" hint="Leave blank to keep existing password">
                    <TextInput
                      value={accessPassword}
                      onChange={setAccessPassword}
                      placeholder="New password…"
                    />
                  </Field>
                )}
              </section>

              {/* Logo */}
              <section className="space-y-3">
                <h3 className="text-xs font-semibold uppercase tracking-wider text-zinc-500">Logo</h3>

                <div className="space-y-3 rounded-md border border-zinc-700/50 bg-zinc-800/50 p-3">
                  <div className="flex gap-2">
                    <Button
                      type="button"
                      variant="outline"
                      size="xs"
                      className="border-zinc-700 text-zinc-300 hover:text-zinc-100"
                      onClick={() => fileInputRef.current?.click()}
                    >
                      Upload image
                    </Button>
                    {logo && (
                      <Button
                        type="button"
                        variant="ghost"
                        size="xs"
                        className="text-zinc-500 hover:text-red-400"
                        onClick={() => setLogo(null)}
                      >
                        Clear
                      </Button>
                    )}
                    <input
                      ref={fileInputRef}
                      type="file"
                      accept="image/*,.svg"
                      className="hidden"
                      onChange={(e) => {
                        const file = e.target.files?.[0];
                        if (!file) return;
                        const reader = new FileReader();
                        reader.onload = (ev) => setLogo(ev.target?.result as string ?? null);
                        reader.readAsDataURL(file);
                        e.target.value = "";
                      }}
                    />
                  </div>

                  <Field label="URL or SVG markup" hint="Paste a remote URL, data URI, or inline SVG">
                    <textarea
                      rows={3}
                      value={logo ?? ""}
                      onChange={(e) => setLogo(e.target.value || null)}
                      placeholder="https://… or <svg>…</svg>"
                      className="w-full resize-none rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 font-mono text-xs text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
                    />
                  </Field>

                  {logo && (
                    <div className="flex items-center justify-center rounded-md border border-zinc-700 bg-zinc-900 p-3">
                      {logo.trimStart().startsWith("<") ? (
                        <div dangerouslySetInnerHTML={{ __html: logo }} className="h-12 w-auto" />
                      ) : (
                        // eslint-disable-next-line @next/next/no-img-element
                        <img src={logo} alt="" className="h-12 w-auto object-contain" />
                      )}
                    </div>
                  )}

                  <div className="grid grid-cols-2 gap-3">
                    <Field label="Position">
                      <NativeSelect value={logoPosition} onChange={(v) => setLogoPosition(v as LogoPosition)}>
                        <option value="top_left">Top left</option>
                        <option value="top_right">Top right</option>
                        <option value="bottom_left">Bottom left</option>
                        <option value="bottom_right">Bottom right</option>
                      </NativeSelect>
                    </Field>
                    <Field label={`Opacity (${logoOpacity}%)`}>
                      <input
                        type="range"
                        min={0}
                        max={100}
                        value={logoOpacity}
                        onChange={(e) => setLogoOpacity(Number(e.target.value))}
                        className="w-full accent-zinc-400 mt-2"
                      />
                    </Field>
                  </div>
                </div>
              </section>

              {/* Recycle policy */}
              <section className="space-y-3">
                <h3 className="text-xs font-semibold uppercase tracking-wider text-zinc-500">Recycle policy</h3>
                <RecyclePolicyEditor
                  policy={recyclePolicy}
                  errors={fieldErrors}
                  onChange={setRecyclePolicy}
                />
              </section>

              {/* Webhook */}
              <section className="space-y-3">
                <h3 className="text-xs font-semibold uppercase tracking-wider text-zinc-500">Webhook</h3>
                <Field label="Webhook URL" hint="POST events to this URL on broadcast changes">
                  <TextInput
                    value={webhookUrl}
                    onChange={setWebhookUrl}
                    placeholder="https://example.com/webhook"
                  />
                </Field>
                {webhookUrl && (
                  <>
                    <Field label="Poll interval (seconds)" hint="How often to check for broadcast changes">
                      <NumberInput
                        value={webhookPollInterval}
                        onChange={setWebhookPollInterval}
                        min={1}
                        placeholder="5"
                      />
                    </Field>

                    <div className="space-y-2">
                      <p className="text-xs text-zinc-400">Payload format</p>
                      <div className="flex gap-1.5 flex-wrap">
                        {(["default", "discord", "slack", "custom"] as WebhookFormat[]).map((fmt) => (
                          <button
                            key={fmt}
                            type="button"
                            onClick={() => {
                              setWebhookFormat(fmt);
                              if (fmt === "discord") setWebhookBodyTemplate(WEBHOOK_PRESETS.discord);
                              else if (fmt === "slack") setWebhookBodyTemplate(WEBHOOK_PRESETS.slack);
                              else if (fmt === "default") setWebhookBodyTemplate("");
                            }}
                            className={`rounded px-2.5 py-1 text-xs font-medium capitalize transition-colors ${
                              webhookFormat === fmt
                                ? "bg-zinc-600 text-zinc-100"
                                : "bg-zinc-800 text-zinc-400 hover:bg-zinc-700 hover:text-zinc-200"
                            }`}
                          >
                            {fmt === "default" ? "K-TV default" : fmt}
                          </button>
                        ))}
                      </div>
                    </div>

                    {webhookFormat !== "default" && (
                      <Field
                        label="Body template (Handlebars)"
                        hint="Context: event, timestamp, channel_id, data.item.title, data.item.duration_secs, …"
                      >
                        <textarea
                          rows={6}
                          value={webhookBodyTemplate}
                          onChange={(e) => {
                            setWebhookBodyTemplate(e.target.value);
                            setWebhookFormat("custom");
                          }}
                          placeholder={'{\n  "text": "Now playing: {{data.item.title}}"\n}'}
                          className="w-full resize-y rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 font-mono text-xs text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
                        />
                      </Field>
                    )}

                    <Field
                      label="Extra headers (JSON)"
                      hint={'e.g. {"Authorization": "Bearer token"}'}
                    >
                      <textarea
                        rows={2}
                        value={webhookHeaders}
                        onChange={(e) => setWebhookHeaders(e.target.value)}
                        placeholder={'{"Authorization": "Bearer xxx"}'}
                        className="w-full resize-none rounded-md border border-zinc-700 bg-zinc-800 px-3 py-2 font-mono text-xs text-zinc-100 placeholder:text-zinc-600 focus:border-zinc-500 focus:outline-none"
                      />
                    </Field>
                  </>
                )}
              </section>
            </div>

            {/* Right column — programming blocks */}
            <div className="flex flex-1 flex-col overflow-hidden">
              {/* Upper half: timeline + block list */}
              <div className="shrink-0 border-b border-zinc-800 px-5 py-4 space-y-3">
                <div className="flex items-center justify-between">
                  <h3 className="text-xs font-semibold uppercase tracking-wider text-zinc-500">
                    Programming blocks
                  </h3>
                  <Button
                    type="button"
                    variant="outline"
                    size="xs"
                    onClick={() => addBlock()}
                    className="border-zinc-700 text-zinc-300 hover:text-zinc-100"
                  >
                    <Plus className="size-3" />
                    Add block
                  </Button>
                </div>

                <BlockTimeline
                  blocks={blocks}
                  selectedId={selectedBlockId}
                  onSelect={setSelectedBlockId}
                  onChange={setBlocks}
                  onCreateBlock={(startMins, durationMins) => addBlock(startMins, durationMins)}
                />

                {blocks.length === 0 ? (
                  <p className="rounded-md border border-dashed border-zinc-700 px-4 py-4 text-center text-xs text-zinc-600">
                    No blocks yet. Drag on the timeline or click Add block.
                  </p>
                ) : (
                  <div className="space-y-1 max-h-48 overflow-y-auto">
                    {blocks.map((block, idx) => (
                      <button
                        key={block.id}
                        type="button"
                        onClick={() => setSelectedBlockId(block.id)}
                        className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-left transition-colors ${
                          block.id === selectedBlockId
                            ? "border border-zinc-600 bg-zinc-700/60"
                            : "border border-transparent hover:bg-zinc-800/60"
                        }`}
                      >
                        <div
                          className="h-2.5 w-2.5 shrink-0 rounded-full"
                          style={{ backgroundColor: BLOCK_COLORS[idx % BLOCK_COLORS.length] }}
                        />
                        <span className="flex-1 truncate text-sm text-zinc-200">
                          {block.name || "Unnamed block"}
                        </span>
                        <span className="shrink-0 font-mono text-[11px] text-zinc-500">
                          {block.start_time.slice(0, 5)} · {block.duration_mins}m
                        </span>
                        <span
                          role="button"
                          onClick={(e) => { e.stopPropagation(); removeBlock(idx); }}
                          className="rounded p-1 text-zinc-600 hover:bg-zinc-700 hover:text-red-400"
                        >
                          <Trash2 className="size-3.5" />
                        </span>
                      </button>
                    ))}
                  </div>
                )}
              </div>

              {/* Lower half: selected block detail */}
              <div className="flex-1 overflow-y-auto px-5 py-4">
                {(() => {
                  if (selectedBlockId === null) {
                    return (
                      <div className="flex h-full items-center justify-center text-sm text-zinc-600">
                        Select a block or create one
                      </div>
                    );
                  }
                  const selectedIdx = blocks.findIndex((b) => b.id === selectedBlockId);
                  const selectedBlock = selectedIdx >= 0 ? blocks[selectedIdx] : null;
                  if (!selectedBlock) {
                    return (
                      <div className="flex h-full items-center justify-center text-sm text-zinc-600">
                        Select a block or create one
                      </div>
                    );
                  }
                  return (
                    <BlockEditor
                      block={selectedBlock}
                      index={selectedIdx}
                      errors={fieldErrors}
                      onChange={(b) => updateBlock(selectedIdx, b)}
                      providers={providers}
                    />
                  );
                })()}
              </div>
            </div>
          </div>

          {/* Footer */}
          <div className="flex items-center justify-between border-t border-zinc-800 px-6 py-4">
            {(error || Object.keys(fieldErrors).length > 0) && (
              <p className="text-xs text-red-400">
                {error ?? "Please fix the errors above"}
              </p>
            )}
            <div className="ml-auto flex gap-2">
              <Button type="button" variant="ghost" onClick={() => onOpenChange(false)} disabled={isPending}>
                Cancel
              </Button>
              <Button type="submit" disabled={isPending}>
                {isPending ? "Saving…" : "Save changes"}
              </Button>
            </div>
          </div>
        </form>
      </SheetContent>
    </Sheet>
  );
}
