"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";

import {
  deleteOddsVersion,
  getAdminPlayTypes,
  getAllConfigVersions,
  getOddsVersion,
  getOddsVersions,
  postOddsVersion,
  publishOddsVersion,
  putOddsItems,
} from "@/api/admin-config";
import { Button } from "@/components/ui/button";
import { ConfigChip, ConfigChipGroup } from "@/modules/config/config-chip-group";
import { ConfigDocPage, ConfigDocToolbar } from "@/modules/config/config-doc-page";

import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { AdminLoadingState } from "@/components/admin/admin-loading-state";
import { ratioToPercentUi } from "@/lib/admin-rate-percent";
import { ConfigReadonlyValue } from "@/modules/config/config-readonly-value";
import { ConfigVersionActions } from "@/modules/config/config-version-actions";
import { ConfigVersionSwitcher } from "@/modules/config/config-version-switcher";

import { ensureAdminPlayTypesLoaded, resolveAdminPlayTypeDisplayName } from "@/lib/admin-play-types";
import { useAsyncEffect } from "@/hooks/use-async-effect";
import { useTranslationRef } from "@/hooks/use-translation-ref";
import { adminHasAnyPermission } from "@/lib/admin-permissions";
import { cn } from "@/lib/utils";
import { PRD_ODDS_MANAGE, PRD_REBATE_MANAGE } from "@/lib/admin-prd";
import { useAdminProfile } from "@/stores/admin-session";
import { LotteryApiBizError } from "@/types/api/errors";
import { pickDefaultConfigVersionId } from "@/lib/config-version-auto-pick";
import type { OddsConfigWorkspace } from "@/modules/config/use-odds-config-workspace";
import type {
  AdminPlayTypeRow,
  ConfigVersionSummary,
  OddsItemRow,
  OddsVersionDetail,
} from "@/types/api/admin-config";

import { OddsConfigDraftBar } from "@/modules/config/doc/odds-config-draft-bar";
import { OddsConfigPlayNav } from "@/modules/config/doc/odds-config-play-nav";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import {
  buildOddsPlayFilterGroups,
  filterOddsPlayTypesByCategory,
  type OddsCategoryTab,
} from "@/modules/config/doc/odds-play-type-groups";
import {
  PRIZE_SCOPE_MULTIPLIER_HINT,
  PRIZE_SCOPE_ORDER,
  prizeScopeLabel,
  type PrizeScopeCode,
} from "@/modules/config/doc/prize-scopes";

type CatTab = OddsCategoryTab;

function oddsMultiplierLabel(oddsValue: number): string {
  return (oddsValue / 10000).toFixed(4);
}

function parseOddsMultiplierInput(raw: string): number {
  const n = Number.parseFloat(raw);
  if (!Number.isFinite(n) || n < 0) {
    return 0;
  }
  const scaled = Math.round(n * 10000);
  return Number.isSafeInteger(scaled) ? scaled : 0;
}

type OddsConfigDocScreenProps = {
  /** 嵌入「赔率与回水」合并页时去掉外层 ConfigDocPage */
  embedded?: boolean;
  /** 合并页：左侧三步骤 + 右侧配置摘要（参考设计稿） */
  mergedLayout?: boolean;
  /** 合并页共享数据层（避免与回水区块重复拉取版本详情） */
  workspace?: OddsConfigWorkspace;
  /** 与回水分区共用版本选择（无 workspace 时） */
  versionId?: string;
  onVersionIdChange?: (id: string) => void;
};

export function OddsConfigDocScreen({
  embedded = false,
  mergedLayout = false,
  workspace,
  versionId: controlledVersionId,
  onVersionIdChange,
}: OddsConfigDocScreenProps) {
  const { t, i18n } = useTranslation(["config", "adminUsers", "common"]);
  const tRef = useTranslationRef(["config", "common"]);
  const profile = useAdminProfile();
  const canManage = adminHasAnyPermission(profile?.permissions, [PRD_ODDS_MANAGE, PRD_REBATE_MANAGE]);
  const [types, setTypes] = useState<AdminPlayTypeRow[]>([]);
  const [list, setList] = useState<ConfigVersionSummary[]>([]);
  const [internalSelectedId, setInternalSelectedId] = useState("");
  const selectedId = workspace?.selectedId ?? controlledVersionId ?? internalSelectedId;
  const setSelectedId = workspace?.setSelectedId ?? onVersionIdChange ?? setInternalSelectedId;
  const [detail, setDetail] = useState<OddsVersionDetail | null>(null);
  const [draftRows, setDraftRows] = useState<OddsItemRow[]>([]);
  const [loadingTypes, setLoadingTypes] = useState(true);
  const [loadingList, setLoadingList] = useState(true);
  const [loadingDetail, setLoadingDetail] = useState(false);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const resolvedTypes = workspace?.types ?? types;
  const resolvedList = workspace?.list ?? list;
  const resolvedDetail = workspace?.detail ?? detail;
  const resolvedDraftRows = workspace?.draftRows ?? draftRows;
  const setResolvedDraftRows = workspace?.setDraftRows ?? setDraftRows;
  const resolvedLoadingTypes = workspace?.loadingTypes ?? loadingTypes;
  const resolvedLoadingList = workspace?.loadingList ?? loadingList;
  const resolvedLoadingDetail = workspace?.loadingDetail ?? loadingDetail;
  const resolvedError = workspace?.error ?? error;

  const [catTab, setCatTab] = useState<CatTab>("all");
  /** User-selected play type. Empty means none selected yet and falls back to the first item in the category. */
  const [playCode, setPlayCode] = useState<string>("");

  const [rollbackOpen, setRollbackOpen] = useState(false);
  const [rollbackTarget, setRollbackTarget] = useState<ConfigVersionSummary | null>(null);
  const [publishConfirmOpen, setPublishConfirmOpen] = useState(false);
  const [activeCompareRows, setActiveCompareRows] = useState<OddsItemRow[]>([]);

  const refreshTypes = useCallback(async () => {
    setLoadingTypes(true);
    try {
      setTypes(await ensureAdminPlayTypesLoaded(getAdminPlayTypes));
    } catch (e) {
      toast.error(
        e instanceof LotteryApiBizError ? e.message : tRef.current("errors.loadFailed", { ns: "common" }),
      );
      setTypes([]);
    } finally {
      setLoadingTypes(false);
    }
  }, [tRef]);

  const refreshList = useCallback(async () => {
    setLoadingList(true);
    setError(null);
    try {
      const d = await getAllConfigVersions(getOddsVersions);
      setList(d.items);
    } catch (e) {
      const msg =
        e instanceof LotteryApiBizError ? e.message : tRef.current("errors.loadFailed", { ns: "common" });
      setError(msg);
      setList([]);
    } finally {
      setLoadingList(false);
    }
  }, [tRef]);

  useAsyncEffect(() => {
    if (workspace) {
      return;
    }
    void Promise.all([refreshTypes(), refreshList()]);
  }, [workspace]);

  const loadDetail = useCallback(async (id: number) => {
    setLoadingDetail(true);
    try {
      const d = await getOddsVersion(id);
      setDetail(d);
      setDraftRows(d.items.map((it) => ({ ...it })));
    } catch (e) {
      toast.error(
        e instanceof LotteryApiBizError ? e.message : tRef.current("errors.loadFailed", { ns: "common" }),
      );
      setDetail(null);
      setDraftRows([]);
    } finally {
      setLoadingDetail(false);
    }
  }, [tRef]);

  useEffect(() => {
    if (workspace) {
      return;
    }
    if (list.length === 0) {
      if (selectedId !== "") {
        queueMicrotask(() => {
          setSelectedId("");
          setDetail(null);
          setDraftRows([]);
        });
      }
      return;
    }
    if (selectedId !== "" && list.some((x) => String(x.id) === selectedId)) {
      return;
    }
    queueMicrotask(() => {
      const pickId = pickDefaultConfigVersionId(list);
      if (pickId) {
        setSelectedId(pickId);
      }
    });
  }, [list, selectedId, setSelectedId, workspace]);

  useEffect(() => {
    if (workspace) {
      return;
    }
    if (selectedId === "") {
      return;
    }
    const id = Number(selectedId);
    if (!Number.isFinite(id)) {
      return;
    }
    queueMicrotask(() => {
      void loadDetail(id);
    });
  }, [selectedId, loadDetail, workspace]);

  const sortedTypes = useMemo(
    () =>
      [...resolvedTypes].sort(
        (a, b) => a.sort_order - b.sort_order || a.play_code.localeCompare(b.play_code),
      ),
    [resolvedTypes],
  );

  const filteredTypes = useMemo(
    () => filterOddsPlayTypesByCategory(catTab, sortedTypes),
    [catTab, sortedTypes],
  );

  const playFilterGroups = useMemo(
    () => buildOddsPlayFilterGroups(catTab, sortedTypes),
    [catTab, sortedTypes],
  );

  const resolvedPlayCode = useMemo(() => {
    if (filteredTypes.length === 0) {
      return "";
    }
    if (playCode && filteredTypes.some((t) => t.play_code === playCode)) {
      return playCode;
    }
    return filteredTypes[0].play_code;
  }, [filteredTypes, playCode]);

  const selectedVersionSummary = useMemo(
    () => resolvedList.find((x) => String(x.id) === selectedId) ?? null,
    [resolvedList, selectedId],
  );
  const isSelectedDetail = resolvedDetail !== null && String(resolvedDetail.id) === selectedId;
  const selectedStatus = isSelectedDetail ? resolvedDetail.status : selectedVersionSummary?.status;
  const isDraft = selectedStatus === "draft";
  const canEditDraft = isDraft && canManage;

  const scopeRows = useMemo(() => {
    const rows: Partial<Record<PrizeScopeCode, OddsItemRow>> = {};
    if (!resolvedPlayCode) {
      return rows;
    }
    for (const row of resolvedDraftRows) {
      if (row.play_code !== resolvedPlayCode) {
        continue;
      }
      const scope = row.prize_scope as PrizeScopeCode;
      if (PRIZE_SCOPE_ORDER.includes(scope)) {
        rows[scope] = row;
      }
    }
    return rows;
  }, [resolvedDraftRows, resolvedPlayCode]);

  const rebatePercentUi = useMemo(() => {
    const first = PRIZE_SCOPE_ORDER.map((s) => scopeRows[s]).find(Boolean);
    if (!first) {
      return "0";
    }
    return ratioToPercentUi(String(first.rebate_rate));
  }, [scopeRows]);

  function rowIndex(play_code: string, prize_scope: string): number {
    return resolvedDraftRows.findIndex((r) => r.play_code === play_code && r.prize_scope === prize_scope);
  }

  function updateOddsRow(idx: number, patch: Partial<OddsItemRow>) {
    setResolvedDraftRows((prev) => prev.map((r, i) => (i === idx ? { ...r, ...patch } : r)));
  }

  function updateOddsForScope(scope: PrizeScopeCode, patch: Partial<OddsItemRow>) {
    const idx = rowIndex(resolvedPlayCode, scope);
    if (idx >= 0) {
      updateOddsRow(idx, patch);
    }
  }

  function setRebateForPlayPercent(percentStr: string) {
    const p = Number.parseFloat(percentStr);
    const rate = Number.isFinite(p) ? p / 100 : 0;
    setResolvedDraftRows((prev) =>
      prev.map((r) =>
        r.play_code === resolvedPlayCode ? { ...r, rebate_rate: String(rate) } : r,
      ),
    );
  }

  async function handleSave() {
    if (!resolvedDetail || !canEditDraft) {
      return;
    }
    setSaving(true);
    try {
      const payload = resolvedDraftRows.map((r) => ({
        play_code: r.play_code,
        prize_scope: r.prize_scope,
        odds_value: r.odds_value,
        rebate_rate: Number.parseFloat(String(r.rebate_rate)) || 0,
        commission_rate: Number.parseFloat(String(r.commission_rate)) || 0,
        currency_code: r.currency_code,
        extra_config_json: r.extra_config_json,
      }));
      const d = await putOddsItems(resolvedDetail.id, payload);
      if (workspace) {
        workspace.applyDetail(d);
      } else {
        setDetail(d);
        setDraftRows(d.items.map((it) => ({ ...it })));
      }
      toast.success(t("versionActions.saveDraft", { ns: "config" }));
      void (workspace?.refreshList() ?? refreshList());
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("versionActions.saveFailed", { ns: "config" }));
    } finally {
      setSaving(false);
    }
  }

  async function handlePublish() {
    if (!resolvedDetail || !canEditDraft) {
      return;
    }
    setSaving(true);
    try {
      const d = await publishOddsVersion(resolvedDetail.id);
      if (workspace) {
        workspace.applyDetail(d);
      } else {
        setDetail(d);
        setDraftRows(d.items.map((it) => ({ ...it })));
      }
      toast.success(t("versionActions.publishCurrent", { ns: "config" }));
      void (workspace?.refreshList() ?? refreshList());
      setSelectedId(String(d.id));
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("odds.publishFailed", { ns: "config" }));
    } finally {
      setSaving(false);
    }
  }

  async function requestPublishConfirm() {
    if (!resolvedDetail || !canEditDraft) {
      return;
    }
    const active = resolvedList.find((x) => x.status === "active");
    if (active && active.id !== resolvedDetail.id) {
      try {
        const d = await getOddsVersion(active.id);
        setActiveCompareRows(d.items);
      } catch {
        setActiveCompareRows([]);
      }
    } else {
      setActiveCompareRows([]);
    }
    setPublishConfirmOpen(true);
  }

  async function handleNewDraft() {
    setSaving(true);
    try {
      const active = resolvedList.find((x) => x.status === "active");
      const d = await postOddsVersion({
        reason: `draft ${new Date().toISOString()}`,
        clone_from_version_id: active?.id ?? null,
      });
      toast.success(t("odds.createDraftSuccess", { ns: "config", version: d.version_no }));
      await (workspace?.refreshList() ?? refreshList());
      setSelectedId(String(d.id));
      if (workspace) {
        workspace.applyDetail(d);
      } else {
        setDetail(d);
        setDraftRows(d.items.map((it) => ({ ...it })));
      }
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("odds.createDraftFailed", { ns: "config" }));
    } finally {
      setSaving(false);
    }
  }

  async function handleRollback() {
    if (!rollbackTarget) {
      return;
    }
    setSaving(true);
    try {
      const d = await postOddsVersion({
        reason: `rollback from v${rollbackTarget.version_no}`,
        clone_from_version_id: rollbackTarget.id,
      });
      toast.success(
        t("versionActions.rollbackSuccess", {
          ns: "config",
          fromVersion: rollbackTarget.version_no,
          version: d.version_no,
        }),
      );
      await (workspace?.refreshList() ?? refreshList());
      setSelectedId(String(d.id));
      if (workspace) {
        workspace.applyDetail(d);
      } else {
        setDetail(d);
        setDraftRows(d.items.map((it) => ({ ...it })));
      }
      setRollbackOpen(false);
      setRollbackTarget(null);
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("versionActions.rollbackFailed", { ns: "config" }));
    } finally {
      setSaving(false);
    }
  }

  async function handleDeleteVersion(row: ConfigVersionSummary) {
    try {
      await deleteOddsVersion(row.id);
      toast.success(t("versionSwitcher.delete", { ns: "config" }));
      await (workspace?.refreshList() ?? refreshList());
    } catch (e) {
      toast.error(e instanceof LotteryApiBizError ? e.message : t("odds.deleteFailed", { ns: "config" }));
      throw e;
    }
  }

  function requestRollback(row: ConfigVersionSummary) {
    setRollbackTarget(row);
    setRollbackOpen(true);
  }

  const publishDiffRows = useMemo(() => {
    if (!resolvedDetail) {
      return [];
    }

    const selectedPlay = resolvedPlayCode;

    return PRIZE_SCOPE_ORDER.map((scope) => {
      const next = resolvedDraftRows.find((r) => r.play_code === selectedPlay && r.prize_scope === scope);
      const old = activeCompareRows.find((r) => r.play_code === selectedPlay && r.prize_scope === scope);
      return {
        scope,
        label: prizeScopeLabel(scope, t),
        oldValue: old?.odds_value ?? null,
        newValue: next?.odds_value ?? null,
      };
    });
  }, [activeCompareRows, resolvedDetail, resolvedDraftRows, resolvedPlayCode, t]);

  const catTabs: { id: CatTab; label: string }[] = [
    { id: "all", label: t("odds.tabs.all", { ns: "config" }) },
    { id: "d4", label: "4D" },
    { id: "d3", label: "3D" },
    { id: "d2", label: "2D" },
  ];

  const activePlayLabel = resolvedPlayCode
    ? resolveAdminPlayTypeDisplayName(resolvedPlayCode, i18n.language, sortedTypes.find((t) => t.play_code === resolvedPlayCode))
    : "—";

  const filtersInner = (
    <>
      <ConfigChipGroup label={t("odds.category", { ns: "config" })}>
        {catTabs.map((tab) => (
          <ConfigChip
            key={tab.id}
            active={catTab === tab.id}
            onClick={() => setCatTab(tab.id)}
          >
            {tab.label}
          </ConfigChip>
        ))}
      </ConfigChipGroup>
      {mergedLayout ? (
        <div className="space-y-4">
          {playFilterGroups.length === 0 ? (
            <span className="text-sm text-muted-foreground">{t("odds.noPlayTypes", { ns: "config" })}</span>
          ) : (
            playFilterGroups.map((group) => (
              <ConfigChipGroup
                key={group.key}
                label={t(`odds.playGroups.${group.key}`, { ns: "config" })}
              >
                {group.types.map((type) => (
                  <ConfigChip
                    key={type.play_code}
                    active={resolvedPlayCode === type.play_code}
                    onClick={() => setPlayCode(type.play_code)}
                  >
                    {resolveAdminPlayTypeDisplayName(type.play_code, i18n.language, type)}
                  </ConfigChip>
                ))}
              </ConfigChipGroup>
            ))
          )}
        </div>
      ) : (
        <ConfigChipGroup label={t("odds.playType", { ns: "config" })}>
          {filteredTypes.length === 0 ? (
            <span className="text-sm text-muted-foreground">{t("odds.noPlayTypes", { ns: "config" })}</span>
          ) : (
            <div className="-mx-1 flex gap-1.5 overflow-x-auto px-1 pb-0.5">
              {filteredTypes.map((type) => (
                <ConfigChip
                  key={type.play_code}
                  active={resolvedPlayCode === type.play_code}
                  onClick={() => setPlayCode(type.play_code)}
                  className="shrink-0"
                >
                  {resolveAdminPlayTypeDisplayName(type.play_code, i18n.language, type)}
                </ConfigChip>
              ))}
            </div>
          )}
        </ConfigChipGroup>
      )}
    </>
  );

  const filtersBlock = mergedLayout ? (
    filtersInner
  ) : (
    <div
      className={cn(
        "space-y-3",
        embedded ? "border-t border-border/50 px-3 py-3 sm:px-4" : "rounded-xl border border-border/60 bg-card p-4",
      )}
    >
      {filtersInner}
    </div>
  );

  const toolbarBlock = (
        <ConfigDocToolbar
          className={embedded ? "rounded-none border-0 shadow-none" : undefined}
          switcher={
            <ConfigVersionSwitcher
              versions={resolvedList}
              selectedId={selectedId}
              onSelectedIdChange={setSelectedId}
              loading={resolvedLoadingList}

              onDeleteVersion={handleDeleteVersion}
              onRollbackVersion={requestRollback}
              rollbackBusy={saving}
            />
          }
          actions={
            <ConfigVersionActions
              isDraft={isDraft}
              canManage={canManage}
              loadingList={resolvedLoadingList}
              loadingDetail={resolvedLoadingDetail}
              saving={saving}
              suppressDraftActions={mergedLayout && isDraft}
              onRefresh={() => void refreshList()}
              onNewDraft={() => void handleNewDraft()}
              onSaveDraft={() => void handleSave()}
              onPublish={() => void requestPublishConfirm()}
            />
          }
        />
  );

  const scopeEditorRows = PRIZE_SCOPE_ORDER.map((scope) => {
    const row = scopeRows[scope];
    const hint = mergedLayout ? null : PRIZE_SCOPE_MULTIPLIER_HINT[scope];
    const idx = row ? rowIndex(resolvedPlayCode, scope) : -1;
    return { scope, row, hint, idx };
  });

  const mergedOddsTable = (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>{t("odds.table.prizeScope", { ns: "config" })}</TableHead>
          <TableHead className="w-[10rem] text-right">{t("odds.table.multiplier", { ns: "config" })}</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {scopeEditorRows.map(({ scope, row, hint, idx }) => (
          <TableRow key={scope}>
            <TableCell className="font-medium">
              {prizeScopeLabel(scope, t)}
              {hint ? <span className="ml-1 text-xs font-normal text-muted-foreground">{hint}</span> : null}
            </TableCell>
            <TableCell className="text-right">
              {row && idx >= 0 ? (
                canEditDraft ? (
                  <Input
                    type="text"
                    inputMode="decimal"
                    className="ml-auto h-9 w-full max-w-[9rem] text-base font-semibold"
                    disabled={saving}
                    value={oddsMultiplierLabel(row.odds_value)}
                    placeholder={t("odds.placeholders.multiplier", { ns: "config" })}
                    onChange={(e) =>
                      updateOddsForScope(scope, {
                        odds_value: parseOddsMultiplierInput(e.target.value),
                      })
                    }
                  />
                ) : (
                  <ConfigReadonlyValue className="ml-auto h-9 w-full max-w-[9rem] justify-center text-base font-semibold">
                    {oddsMultiplierLabel(row.odds_value)}
                  </ConfigReadonlyValue>
                )
              ) : (
                <span className="text-xs text-destructive">
                  {t("odds.missingScopeRow", { ns: "config", scope })}
                </span>
              )}
            </TableCell>
          </TableRow>
        ))}
        <TableRow>
          <TableCell className="font-medium">{t("odds.rebateRate", { ns: "config" })}</TableCell>
          <TableCell className="text-right">
            {canEditDraft ? (
              <Input
                type="text"
                inputMode="decimal"
                className="ml-auto h-9 w-full max-w-[9rem] text-base font-semibold"
                disabled={saving}
                value={rebatePercentUi}
                placeholder={t("odds.placeholders.rebateRate", { ns: "config" })}
                onChange={(e) => setRebateForPlayPercent(e.target.value)}
              />
            ) : (
              <ConfigReadonlyValue className="ml-auto h-9 w-full max-w-[9rem] justify-center text-base font-semibold">
                {rebatePercentUi}
              </ConfigReadonlyValue>
            )}
          </TableCell>
        </TableRow>
      </TableBody>
    </Table>
  );

  const classicOddsGrid = (
    <div className="grid grid-cols-2 gap-x-4 gap-y-4 sm:grid-cols-3">
      {scopeEditorRows.map(({ scope, row, hint, idx }) => (
        <div key={scope} className="grid min-w-0 gap-1.5">
          <Label className="truncate text-xs font-medium text-muted-foreground">
            {prizeScopeLabel(scope, t)}
            {hint ? <span className="ml-1 font-normal">{hint}</span> : null}
          </Label>
          {row && idx >= 0 ? (
            canEditDraft ? (
              <Input
                type="text"
                inputMode="decimal"
                className="h-9 w-full text-base font-semibold"
                disabled={saving}
                value={oddsMultiplierLabel(row.odds_value)}
                placeholder={t("odds.placeholders.multiplier", { ns: "config" })}
                onChange={(e) =>
                  updateOddsForScope(scope, {
                    odds_value: parseOddsMultiplierInput(e.target.value),
                  })
                }
              />
            ) : (
              <ConfigReadonlyValue className="h-9 w-full justify-center text-base font-semibold">
                {oddsMultiplierLabel(row.odds_value)}
              </ConfigReadonlyValue>
            )
          ) : (
            <p className="text-xs text-destructive">{t("odds.missingScopeRow", { ns: "config", scope })}</p>
          )}
        </div>
      ))}
      <div className="grid min-w-0 gap-1.5">
        <Label className="truncate text-xs font-medium text-muted-foreground">
          {t("odds.rebateRate", { ns: "config" })}
        </Label>
        {canEditDraft ? (
          <Input
            type="text"
            inputMode="decimal"
            className="h-9 w-full text-base font-semibold"
            disabled={saving}
            value={rebatePercentUi}
            placeholder={t("odds.placeholders.rebateRate", { ns: "config" })}
            onChange={(e) => setRebateForPlayPercent(e.target.value)}
          />
        ) : (
          <ConfigReadonlyValue className="h-9 w-full justify-center text-base font-semibold">
            {rebatePercentUi}
          </ConfigReadonlyValue>
        )}
      </div>
    </div>
  );

  const mainBlock = (
    <>
      {resolvedError ? <p className="text-sm text-destructive">{resolvedError}</p> : null}

      {resolvedLoadingDetail || resolvedLoadingTypes ? (
        <AdminLoadingState
          className={cn(mergedLayout ? "py-6" : "py-8")}
          minHeight="6rem"
          label={t("odds.loadingDetails", { ns: "config" })}
        />
      ) : resolvedPlayCode ? (
        <div className={cn(!mergedLayout && embedded ? "rounded-xl border border-border/60 bg-card p-4" : undefined)}>
          {mergedLayout ? mergedOddsTable : classicOddsGrid}
        </div>
      ) : null}
    </>
  );

  const dialogs = (
    <>
      <Dialog open={rollbackOpen} onOpenChange={setRollbackOpen}>
        <DialogContent showCloseButton className="sm:max-w-md">
          <DialogHeader>
            <DialogTitle>{t("versionActions.rollbackDialog.title", { ns: "config" })}</DialogTitle>
            <DialogDescription>
              {t("versionActions.rollbackDialog.description", {
                ns: "config",
                version: rollbackTarget?.version_no ?? "—",
              })}
            </DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button type="button" variant="outline" onClick={() => setRollbackOpen(false)}>
              {t("actions.cancel", { ns: "adminUsers" })}
            </Button>
            <Button type="button" onClick={() => void handleRollback()} disabled={!rollbackTarget || saving}>
              {t("versionActions.rollbackDialog.confirm", { ns: "config" })}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      <Dialog open={publishConfirmOpen} onOpenChange={setPublishConfirmOpen}>
        <DialogContent showCloseButton className="sm:max-w-lg">
          <DialogHeader>
            <DialogTitle>{t("odds.publishDialog.title", { ns: "config" })}</DialogTitle>
            <DialogDescription>
              {t("odds.publishDialog.description", { ns: "config" })}
            </DialogDescription>
          </DialogHeader>
          <div className="rounded-lg border">
            <div className="grid grid-cols-3 border-b bg-muted/40 px-3 py-2 text-sm font-medium">
              <span>{t("odds.publishDialog.columns.prizeScope", { ns: "config" })}</span>
              <span className="text-right">{t("odds.publishDialog.columns.currentActive", { ns: "config" })}</span>
              <span className="text-right">{t("odds.publishDialog.columns.afterPublish", { ns: "config" })}</span>
            </div>
            {publishDiffRows.map((row) => (
              <div key={row.scope} className="grid grid-cols-3 px-3 py-2 text-sm">
                <span>{row.label}</span>
                <span className="text-right text-base font-semibold">
                  {row.oldValue === null ? "—" : oddsMultiplierLabel(row.oldValue)}
                </span>
                <span className="text-right text-base font-semibold">
                  {row.newValue === null ? "—" : oddsMultiplierLabel(row.newValue)}
                </span>
              </div>
            ))}
          </div>
          <DialogFooter>
            <Button type="button" variant="outline" onClick={() => setPublishConfirmOpen(false)}>
              {t("actions.cancel", { ns: "adminUsers" })}
            </Button>
            <Button
              type="button"
              disabled={saving}
              onClick={() => {
                setPublishConfirmOpen(false);
                void handlePublish();
              }}
            >
              {t("odds.publishDialog.confirm", { ns: "config" })}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );

  if (embedded && mergedLayout) {
    return (
      <div className="space-y-4">
        {toolbarBlock}
        <div className="grid gap-0 rounded-lg border border-border/60 lg:grid-cols-[minmax(0,13rem)_minmax(0,1fr)]">
          <aside className="border-b border-border/50 px-4 py-4 lg:border-r lg:border-b-0">
            <OddsConfigPlayNav
              catTab={catTab}
              onCatTabChange={setCatTab}
              onPlayCodeChange={setPlayCode}
              types={sortedTypes}
              resolvedPlayCode={resolvedPlayCode}
            />
          </aside>
          <div className="min-w-0">
            <div className="border-b border-border/50 px-4 py-2.5 sm:px-5">
              <h3 className="text-base font-semibold">{activePlayLabel}</h3>
            </div>
            <div className="px-4 py-4 sm:px-5">{mainBlock}</div>
            {isDraft && canManage ? (
              <OddsConfigDraftBar
                saving={saving}
                loadingDetail={resolvedLoadingDetail}
                onSave={() => void handleSave()}
                onPublish={() => void requestPublishConfirm()}
              />
            ) : null}
          </div>
        </div>
        {dialogs}
      </div>
    );
  }

  if (embedded) {
    return (
      <div className="space-y-4">
        <div className="overflow-hidden rounded-xl border border-border/60 bg-card">
          {toolbarBlock}
          {filtersBlock}
        </div>
        {mainBlock}
        {dialogs}
      </div>
    );
  }

  return (
    <ConfigDocPage
      title={t("nav.items.odds", { ns: "config" })}
      filters={filtersBlock}
      toolbar={toolbarBlock}
    >
      {mainBlock}
      {dialogs}
    </ConfigDocPage>
  );
}
