"use client";

import React, { useState, useEffect, useCallback } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
  Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
} from "@/components/ui/dialog";
import {
  Plus, Trash2, Save, Loader2, FileSpreadsheet, Download, FileText,
} from "lucide-react";
import { apiClient } from "@/lib/api/client";
import { reportApi } from "@/lib/api/reportApi";
import { useConfirmDialog } from "@/components/common/ConfirmDialog";
import { exportToExcel } from "@/lib/utils/excelExport";
import { useAuth } from "@/hooks/useAuth";
import { toast } from "sonner";
import { FormDatePicker } from "@/components/screen/filters/FormDatePicker";
import { DynamicSearchFilter, FilterValue } from "@/components/common/DynamicSearchFilter";
import { DataGrid, DataGridColumn } from "@/components/common/DataGrid";
import { ExcelUploadModal } from "@/components/common/ExcelUploadModal";
import {
  ResizablePanelGroup, ResizablePanel, ResizableHandle,
} from "@/components/ui/resizable";
import { ReportInlineViewer } from "@/components/report/ReportInlineViewer";
import { ReportMaster, ComponentConfig } from "@/types/report";

const MASTER_TABLE = "quote_mng";

const fmt = (val: string) => {
  const num = val.replace(/[^\d.-]/g, "");
  if (!num) return "";
  const parts = num.split(".");
  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  return parts.join(".");
};
const pn = (val: string) => val.replace(/,/g, "");

const GRID_COLUMNS: DataGridColumn[] = [
  { key: "quote_no", label: "견적번호", width: "w-[120px]" },
  { key: "customer_name", label: "거래처명", width: "w-[150px]" },
  { key: "quote_date", label: "견적일자", width: "w-[110px]" },
  { key: "valid_until", label: "유효기한", width: "w-[110px]" },
  { key: "total_amount", label: "견적금액", width: "w-[120px]", formatNumber: true, align: "right" },
  { key: "status_label", label: "상태", width: "w-[90px]" },
  { key: "manager", label: "담당자", width: "w-[100px]" },
  { key: "domestic_type", label: "국내/국외", width: "w-[90px]" },
];

const STATUS_MAP: Record<string, string> = {
  draft: "작성중", pending: "검토중", approved: "승인", rejected: "반려", converted: "수주전환",
};

const EMPTY_ITEM = {
  item_code: "", item_name: "", spec: "", qty: "1", unit: "EA",
  request_length: "", unit_price: "0", supply_amount: "0", vat_amount: "0", total_amount: "0", notes: "",
};

export default function QuoteManagementPage() {
  const { user } = useAuth();
  const { confirm, ConfirmDialogComponent } = useConfirmDialog();

  const [quotes, setQuotes] = useState<any[]>([]);
  const [loading, setLoading] = useState(false);
  const [totalCount, setTotalCount] = useState(0);
  const [searchFilters, setSearchFilters] = useState<FilterValue[]>([]);
  const [checkedIds, setCheckedIds] = useState<string[]>([]);
  const [selectedRow, setSelectedRow] = useState<any | null>(null);

  // 컴포넌트 클릭 편집 모달
  const [editComp, setEditComp] = useState<ComponentConfig | null>(null);
  const [editValues, setEditValues] = useState<Record<string, string>>({});
  const [items, setItems] = useState<(typeof EMPTY_ITEM)[]>([]);
  const [saving, setSaving] = useState(false);

  // 기본정보 모달
  const [basicInfoOpen, setBasicInfoOpen] = useState(false);
  const [basicForm, setBasicForm] = useState({ quote_date: "", valid_until: "", status: "draft" });

  // 엑셀 / 리포트
  const [excelOpen, setExcelOpen] = useState(false);
  const [reportList, setReportList] = useState<ReportMaster[]>([]);
  const [selectedReportId, setSelectedReportId] = useState<string | null>(null);
  const [reportKey, setReportKey] = useState(0);

  // 품목 검색
  const [itemSearchOpen, setItemSearchOpen] = useState(false);
  const [itemSearchKeyword, setItemSearchKeyword] = useState("");
  const [itemSearchResults, setItemSearchResults] = useState<any[]>([]);
  const [itemSearchLoading, setItemSearchLoading] = useState(false);
  const [itemSelectedMap, setItemSelectedMap] = useState<Map<string, any>>(new Map());

  // 거래처 검색
  const [custSearchOpen, setCustSearchOpen] = useState(false);
  const [custSearchKeyword, setCustSearchKeyword] = useState("");
  const [custSearchResults, setCustSearchResults] = useState<any[]>([]);
  const [custSearchLoading, setCustSearchLoading] = useState(false);

  // 사원(담당자) 검색
  const [userSearchOpen, setUserSearchOpen] = useState(false);
  const [userSearchKeyword, setUserSearchKeyword] = useState("");
  const [userSearchResults, setUserSearchResults] = useState<any[]>([]);
  const [userSearchLoading, setUserSearchLoading] = useState(false);

  // ── 데이터 로드 ──

  const mapRow = (r: any) => ({
    ...r, id: String(r.objid), status_label: STATUS_MAP[r.status] ?? r.status, total_amount: Number(r.total_amount || 0),
  });

  const fetchQuotes = useCallback(async () => {
    if (!user) return;
    setLoading(true);
    try {
      const params: Record<string, string> = {};
      searchFilters.forEach((f) => { if (f.value) params[f.columnName] = f.value; });
      const res = await apiClient.get("/quotes/list", { params });
      const mapped = (res.data?.data ?? []).map(mapRow);
      setQuotes(mapped);
      setTotalCount(mapped.length);
    } catch { toast.error("견적 목록 조회 실패"); }
    finally { setLoading(false); }
  }, [user, searchFilters]);

  useEffect(() => { fetchQuotes(); }, [fetchQuotes]);

  useEffect(() => {
    (async () => {
      try {
        const res = await reportApi.getReports({ page: 1, limit: 100 });
        if (res.success) {
          const items = res.data.items ?? [];
          setReportList(items);
          if (items.length > 0 && !selectedReportId) setSelectedReportId(items[0].report_id);
        }
      } catch { /* 무시 */ }
    })();
  }, []);

  // ── "신규" → DB에 draft 즉시 생성 → 자동 선택 ──

  const handleCreate = async () => {
    try {
      const numRes = await apiClient.get("/quotes/generate-number");
      const quoteNo = numRes.data?.data?.quoteNo ?? "";
      const createRes = await apiClient.post("/quotes", {
        quote_no: quoteNo,
        quote_date: new Date().toISOString().split("T")[0],
        status: "draft",
        customer_name: "",
        items: [],
      });
      toast.success("신규 견적이 생성되었습니다. 우측 양식에서 각 영역을 클릭하여 입력하세요.");

      const listRes = await apiClient.get("/quotes/list");
      const mapped = (listRes.data?.data ?? []).map(mapRow);
      setQuotes(mapped);
      setTotalCount(mapped.length);

      const newObjid = createRes.data?.data?.objid;
      const newRow = newObjid ? mapped.find((r: any) => r.objid === newObjid) : mapped[0];
      if (newRow) setSelectedRow(newRow);
    } catch { toast.error("견적 생성 실패"); }
  };

  // ── 삭제 ──

  const handleDelete = async () => {
    if (checkedIds.length === 0) { toast.info("삭제할 견적을 선택하세요."); return; }
    const ok = await confirm(`${checkedIds.length}건의 견적을 삭제하시겠습니까?`, { variant: "destructive", confirmText: "삭제" });
    if (!ok) return;
    try {
      for (const id of checkedIds) await apiClient.delete(`/quotes/${id}`);
      toast.success("삭제 완료");
      setCheckedIds([]);
      setSelectedRow(null);
      fetchQuotes();
    } catch { toast.error("삭제 실패"); }
  };

  // ── 컴포넌트 클릭 → 편집 모달 ──

  const handleComponentClick = async (comp: ComponentConfig) => {
    if (!selectedRow) return;
    setEditComp(comp);

    if (comp.type === "table") {
      // 테이블 → 품목 편집
      try {
        const res = await apiClient.get(`/quotes/${selectedRow.objid}`);
        const d = res.data?.data;
        setItems(
          (d?.items || []).length > 0
            ? d.items.map((it: any) => ({
                item_code: it.item_code || "", item_name: it.item_name || "", spec: it.spec || "",
                qty: String(it.qty ?? 1), unit: it.unit || "EA",
                request_length: it.request_length ? String(it.request_length) : "",
                unit_price: String(it.unit_price ?? 0), supply_amount: String(it.supply_amount ?? 0),
                vat_amount: String(it.vat_amount ?? 0), total_amount: String(it.total_amount ?? 0),
                notes: it.notes || "",
              }))
            : [{ ...EMPTY_ITEM }],
        );
      } catch { setItems([{ ...EMPTY_ITEM }]); }
    } else if (comp.type === "card") {
      const cardItems = (comp as any).cardItems ?? [];
      // 우측 카드 (회사정보 + 담당자) → 담당자 선택
      const hasCompanyField = cardItems.some((ci: any) =>
        ["company_name_self", "ceo_self", "biz_no_self", "address_self"].includes(ci.fieldName || "")
      );
      if (hasCompanyField) {
        setUserSearchKeyword("");
        setUserSearchResults([]);
        setUserSearchOpen(true);
        searchUsers();
        return;
      }
      // 좌측 카드 (거래처) → 거래처 검색
      const hasCustomerField = cardItems.some((ci: any) =>
        ["customer_name", "customer_ceo", "customer_biz_no"].includes(ci.fieldName || "")
      );
      if (hasCustomerField) {
        setCustSearchKeyword("");
        setCustSearchResults([]);
        setCustSearchOpen(true);
        searchCustomers();
        return;
      }
      // 기타 카드
      const vals: Record<string, string> = {};
      cardItems.forEach((ci: any) => {
        if (ci.fieldName) vals[ci.fieldName] = selectedRow[ci.fieldName] ?? ci.value ?? "";
      });
      setEditValues(vals);
    } else if (comp.type === "text" || comp.type === "label") {
      // 텍스트 → 기본정보 모달 하나로 통합
      const basicFields = ["quote_no", "quote_date", "valid_until", "status"];
      if (comp.fieldName && basicFields.includes(comp.fieldName)) {
        // 기본정보 모달
        const detail = await apiClient.get(`/quotes/${selectedRow.objid}`).then(r => r.data?.data).catch(() => null);
        setBasicForm({
          quote_date: detail?.quote_date || "",
          valid_until: detail?.valid_until || "",
          status: detail?.status || "draft",
        });
        setBasicInfoOpen(true);
        setEditComp(null);
        return;
      }
      // 기타 텍스트 (제목 등)
      toast.info("이 영역은 리포트 디자이너에서 수정하세요.");
      setEditComp(null);
      return;
    } else if (comp.type === "calculation") {
      // 계산은 읽기 전용 안내
      toast.info("계산 컴포넌트는 품목 데이터에서 자동 계산됩니다.");
      setEditComp(null);
    } else if (comp.type === "signature" || comp.type === "stamp") {
      toast.info("서명/도장은 리포트 디자이너에서 설정하세요.");
      setEditComp(null);
    } else {
      setEditValues({});
    }
  };

  // ── 컴포넌트 편집 저장 ──

  const handleEditSave = async () => {
    if (!selectedRow || !editComp) return;
    setSaving(true);
    try {
      if (editComp.type === "table") {
        // 품목 저장 — 기존 견적 데이터 불러와서 품목만 교체
        const detailRes = await apiClient.get(`/quotes/${selectedRow.objid}`);
        const existing = detailRes.data?.data ?? {};
        const payload = {
          ...existing,
          items: items.filter((it) => it.item_name).map((it) => ({
            item_code: it.item_code, item_name: it.item_name, spec: it.spec,
            qty: Number(pn(it.qty)) || 0, unit: it.unit,
            request_length: it.request_length ? Number(it.request_length) : null,
            unit_price: Number(pn(it.unit_price)) || 0,
            supply_amount: Number(pn(it.supply_amount)) || 0,
            vat_amount: Number(pn(it.vat_amount)) || 0,
            total_amount: Number(pn(it.total_amount)) || 0,
            notes: it.notes,
          })),
        };
        await apiClient.put(`/quotes/${selectedRow.objid}`, payload);
      } else {
        // 텍스트/카드 → 해당 필드만 업데이트
        const detailRes = await apiClient.get(`/quotes/${selectedRow.objid}`);
        const existing = detailRes.data?.data ?? {};
        const payload = { ...existing, ...editValues };
        // items는 기존 유지
        payload.items = (existing.items || []).map((it: any) => ({
          item_code: it.item_code, item_name: it.item_name, spec: it.spec,
          qty: Number(it.qty ?? 0), unit: it.unit,
          request_length: it.request_length || null,
          unit_price: Number(it.unit_price ?? 0),
          supply_amount: Number(it.supply_amount ?? 0),
          vat_amount: Number(it.vat_amount ?? 0),
          total_amount: Number(it.total_amount ?? 0),
          notes: it.notes,
        }));
        await apiClient.put(`/quotes/${selectedRow.objid}`, payload);
      }

      toast.success("저장되었습니다.");
      setEditComp(null);

      // 목록 + 리포트 갱신
      const listRes = await apiClient.get("/quotes/list");
      const mapped = (listRes.data?.data ?? []).map(mapRow);
      setQuotes(mapped);
      setTotalCount(mapped.length);
      const updated = mapped.find((r: any) => r.objid === selectedRow.objid);
      if (updated) setSelectedRow(updated);
      setReportKey((k) => k + 1);
    } catch { toast.error("저장 실패"); }
    finally { setSaving(false); }
  };

  // ── 거래처 검색 ──

  const searchCustomers = async () => {
    setCustSearchLoading(true);
    try {
      const filters: any[] = [];
      if (custSearchKeyword) {
        filters.push({ columnName: "customer_name", operator: "contains", value: custSearchKeyword });
      }
      const res = await apiClient.post("/table-management/tables/customer_mng/data", {
        page: 1, size: 50,
        dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
        autoFilter: true,
      });
      const resData = res.data?.data;
      setCustSearchResults(resData?.data || resData?.rows || []);
    } catch { toast.error("거래처 조회 실패"); }
    finally { setCustSearchLoading(false); }
  };

  const selectCustomer = async (cust: any) => {
    if (!selectedRow) return;
    setSaving(true);
    try {
      const detailRes = await apiClient.get(`/quotes/${selectedRow.objid}`);
      const existing = detailRes.data?.data ?? {};
      const payload = {
        ...existing,
        customer_objid: cust.objid || null,
        customer_name: cust.customer_name || "",
        customer_ceo: cust.contact_person || "",
        customer_biz_no: cust.business_number || "",
        customer_address: cust.address || "",
        customer_contact: cust.contact_person || "",
        customer_phone: cust.contact_phone || "",
        items: (existing.items || []).map((it: any) => ({
          item_code: it.item_code, item_name: it.item_name, spec: it.spec,
          qty: Number(it.qty ?? 0), unit: it.unit,
          request_length: it.request_length || null,
          unit_price: Number(it.unit_price ?? 0),
          supply_amount: Number(it.supply_amount ?? 0),
          vat_amount: Number(it.vat_amount ?? 0),
          total_amount: Number(it.total_amount ?? 0), notes: it.notes,
        })),
      };
      await apiClient.put(`/quotes/${selectedRow.objid}`, payload);
      toast.success(`${payload.customer_name} 거래처 적용 완료`);
      setCustSearchOpen(false);
      setEditComp(null);

      // 목록 + 리포트 갱신
      const listRes = await apiClient.get("/quotes/list");
      const mapped = (listRes.data?.data ?? []).map(mapRow);
      setQuotes(mapped);
      setTotalCount(mapped.length);
      const updated = mapped.find((r: any) => r.objid === selectedRow.objid);
      if (updated) setSelectedRow(updated);
      setReportKey((k) => k + 1);
    } catch { toast.error("저장 실패"); }
    finally { setSaving(false); }
  };

  // ── 기본정보 저장 ──

  const handleBasicInfoSave = async () => {
    if (!selectedRow) return;
    setSaving(true);
    try {
      const detailRes = await apiClient.get(`/quotes/${selectedRow.objid}`);
      const existing = detailRes.data?.data ?? {};
      const payload = {
        ...existing,
        quote_date: basicForm.quote_date,
        valid_until: basicForm.valid_until,
        status: basicForm.status,
        items: (existing.items || []).map((it: any) => ({
          item_code: it.item_code, item_name: it.item_name, spec: it.spec,
          qty: Number(it.qty ?? 0), unit: it.unit,
          request_length: it.request_length || null,
          unit_price: Number(it.unit_price ?? 0),
          supply_amount: Number(it.supply_amount ?? 0),
          vat_amount: Number(it.vat_amount ?? 0),
          total_amount: Number(it.total_amount ?? 0), notes: it.notes,
        })),
      };
      await apiClient.put(`/quotes/${selectedRow.objid}`, payload);
      toast.success("기본정보 저장 완료");
      setBasicInfoOpen(false);

      const listRes = await apiClient.get("/quotes/list");
      const mapped = (listRes.data?.data ?? []).map(mapRow);
      setQuotes(mapped);
      setTotalCount(mapped.length);
      const updated = mapped.find((r: any) => r.objid === selectedRow.objid);
      if (updated) setSelectedRow(updated);
      setReportKey((k) => k + 1);
    } catch { toast.error("저장 실패"); }
    finally { setSaving(false); }
  };

  // ── 사원(담당자) 검색 ──

  const searchUsers = async () => {
    setUserSearchLoading(true);
    try {
      const filters: any[] = [];
      if (userSearchKeyword) {
        filters.push({ columnName: "user_name", operator: "contains", value: userSearchKeyword });
      }
      const res = await apiClient.post("/table-management/tables/user_info/data", {
        page: 1, size: 50,
        dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
        autoFilter: true,
      });
      const resData = res.data?.data;
      setUserSearchResults(resData?.data || resData?.rows || []);
    } catch { toast.error("사원 조회 실패"); }
    finally { setUserSearchLoading(false); }
  };

  const selectUser = async (usr: any) => {
    if (!selectedRow) return;
    setSaving(true);
    try {
      const detailRes = await apiClient.get(`/quotes/${selectedRow.objid}`);
      const existing = detailRes.data?.data ?? {};
      const payload = {
        ...existing,
        manager: usr.user_name || "",
        items: (existing.items || []).map((it: any) => ({
          item_code: it.item_code, item_name: it.item_name, spec: it.spec,
          qty: Number(it.qty ?? 0), unit: it.unit,
          request_length: it.request_length || null,
          unit_price: Number(it.unit_price ?? 0),
          supply_amount: Number(it.supply_amount ?? 0),
          vat_amount: Number(it.vat_amount ?? 0),
          total_amount: Number(it.total_amount ?? 0), notes: it.notes,
        })),
      };
      await apiClient.put(`/quotes/${selectedRow.objid}`, payload);
      toast.success(`담당자: ${payload.manager} 적용`);
      setUserSearchOpen(false);
      setEditComp(null);

      const listRes = await apiClient.get("/quotes/list");
      const mapped = (listRes.data?.data ?? []).map(mapRow);
      setQuotes(mapped);
      setTotalCount(mapped.length);
      const updated = mapped.find((r: any) => r.objid === selectedRow.objid);
      if (updated) setSelectedRow(updated);
      setReportKey((k) => k + 1);
    } catch { toast.error("저장 실패"); }
    finally { setSaving(false); }
  };

  // ── 품목 검색 ──

  const searchItemInfo = async () => {
    setItemSearchLoading(true);
    try {
      const filters: any[] = [];
      if (itemSearchKeyword) {
        filters.push({ columnName: "item_name", operator: "contains", value: itemSearchKeyword });
      }
      const res = await apiClient.post("/table-management/tables/item_info/data", {
        page: 1, size: 50,
        dataFilter: filters.length > 0 ? { enabled: true, filters } : undefined,
        autoFilter: true,
      });
      const resData = res.data?.data;
      setItemSearchResults(resData?.data || resData?.rows || []);
    } catch { toast.error("품목 조회 실패"); }
    finally { setItemSearchLoading(false); }
  };

  const toggleItemSelect = (row: any) => {
    const key = row.item_number || row.objid || row.id;
    setItemSelectedMap((prev) => {
      const next = new Map(prev);
      if (next.has(key)) next.delete(key); else next.set(key, row);
      return next;
    });
  };

  const addSelectedItemsToQuote = () => {
    const selected = Array.from(itemSelectedMap.values());
    if (selected.length === 0) { toast.info("품목을 선택하세요."); return; }
    const newItems = selected.map((item) => calcItem({
      item_code: item.item_number || item.item_code || "",
      item_name: item.item_name || "",
      spec: item.spec || item.standard || "",
      qty: "1",
      unit: item.unit || "EA",
      request_length: "",
      unit_price: String(item.selling_price || item.standard_price || 0),
      supply_amount: "0",
      vat_amount: "0",
      total_amount: "0",
      notes: "",
    }));
    setItems((prev) => [...prev, ...newItems]);
    setItemSearchOpen(false);
    setItemSelectedMap(new Map());
    setItemSearchKeyword("");
    toast.success(`${selected.length}건 품목 추가`);
  };

  // ── 품목 계산 ──

  const calcItem = (item: typeof EMPTY_ITEM) => {
    const qty = Number(pn(item.qty)) || 0;
    const price = Number(pn(item.unit_price)) || 0;
    const supply = qty * price;
    const vat = Math.round(supply * 0.1);
    return { ...item, supply_amount: String(supply), vat_amount: String(vat), total_amount: String(supply + vat) };
  };
  const updateItem = (idx: number, field: string, value: string) => {
    setItems((prev) => {
      const next = [...prev];
      next[idx] = { ...next[idx], [field]: value };
      if (field === "qty" || field === "unit_price") next[idx] = calcItem(next[idx]);
      return next;
    });
  };
  const addItem = () => setItems((prev) => [...prev, { ...EMPTY_ITEM }]);
  const removeItem = (idx: number) => setItems((prev) => prev.filter((_, i) => i !== idx));
  const totalSupply = items.reduce((s, it) => s + (Number(pn(it.supply_amount)) || 0), 0);
  const totalVat = items.reduce((s, it) => s + (Number(pn(it.vat_amount)) || 0), 0);
  const totalAmount = totalSupply + totalVat;

  // ── 행 클릭 ──

  const handleRowClick = (row: any) => setSelectedRow(row);

  const contextParams = selectedRow
    ? { "$1": selectedRow.objid, "$2": user?.companyCode || "COMPANY_7", objid: selectedRow.objid, quote_no: selectedRow.quote_no, customer_name: selectedRow.customer_name, quote_date: selectedRow.quote_date }
    : undefined;

  // ── 편집 모달 제목/타입 판별 ──

  const getModalTitle = () => {
    if (!editComp) return "";
    if (editComp.type === "table") return "견적 품목";
    if (editComp.type === "card") {
      const items = (editComp as any).cardItems ?? [];
      const title = (editComp as any).headerText || (editComp as any).title || "";
      if (title) return title;
      if (items.length > 0) return items[0].label ?? "카드 정보";
      return "카드 정보";
    }
    if (editComp.type === "text" || editComp.type === "label") {
      if (editComp.fieldName) return editComp.fieldName;
      return "텍스트 편집";
    }
    return editComp.type;
  };

  // ── JSX ──

  return (
    <div className="flex h-full flex-col gap-3 p-3">
      <DynamicSearchFilter tableName={MASTER_TABLE} filterId="quote-mng-filter"
        onFilterChange={(filters) => setSearchFilters(filters)} dataCount={totalCount} />

      <ResizablePanelGroup direction="horizontal" className="flex-1 overflow-hidden rounded-lg border">
        {/* 좌측: 견적 목록 */}
        <ResizablePanel defaultSize={55} minSize={35}>
          <div className="flex h-full flex-col">
            <div className="flex items-center justify-between border-b bg-white px-3 py-2">
              <span className="text-sm font-semibold text-gray-700">
                견적 목록 <span className="text-gray-400">{totalCount}건</span>
              </span>
              <div className="flex items-center gap-1.5">
                <Button variant="outline" size="sm" className="gap-1 text-xs" onClick={() => setExcelOpen(true)}>
                  <FileSpreadsheet className="h-3.5 w-3.5" /> 업로드
                </Button>
                <Button variant="outline" size="sm" className="gap-1 text-xs"
                  onClick={() => quotes.length > 0 ? exportToExcel(quotes, "견적목록.xlsx", "견적목록") : toast.info("데이터 없음")}>
                  <Download className="h-3.5 w-3.5" /> 다운로드
                </Button>
                <Button size="sm" className="gap-1 text-xs" onClick={handleCreate}>
                  <Plus className="h-3.5 w-3.5" /> 신규
                </Button>
                <Button variant="destructive" size="sm" className="gap-1 text-xs" onClick={handleDelete}>
                  <Trash2 className="h-3.5 w-3.5" /> 삭제
                </Button>
              </div>
            </div>
            <div className="flex-1 overflow-hidden">
              <DataGrid gridId="quote-mng" columns={GRID_COLUMNS} data={quotes} showCheckbox tableName={MASTER_TABLE}
                checkedIds={checkedIds} onCheckedChange={setCheckedIds}
                onRowClick={handleRowClick} selectedId={selectedRow?.objid ? String(selectedRow.objid) : null} />
            </div>
          </div>
        </ResizablePanel>

        <ResizableHandle withHandle />

        {/* 우측: 리포트 뷰어 — 컴포넌트 클릭 가능 */}
        <ResizablePanel defaultSize={45} minSize={25}>
          <div className="flex h-full flex-col">
            <div className="flex items-center gap-2 border-b bg-white px-3 py-2">
              <FileText className="h-4 w-4 text-gray-500" />
              <span className="text-sm font-semibold text-gray-700">견적서</span>
              <Select value={selectedReportId ?? ""} onValueChange={(v) => setSelectedReportId(v || null)}>
                <SelectTrigger className="ml-auto h-7 w-[180px] text-xs">
                  <SelectValue placeholder="리포트 양식 선택" />
                </SelectTrigger>
                <SelectContent>
                  {reportList.map((r) => (
                    <SelectItem key={r.report_id} value={r.report_id} className="text-xs">
                      {r.report_name_kor || r.report_name_eng || r.report_id}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>

            <div className="flex-1 overflow-hidden">
              {!selectedRow ? (
                <div className="flex h-full items-center justify-center text-center text-gray-400">
                  <div>
                    <FileText className="mx-auto mb-3 h-12 w-12 opacity-30" />
                    <p className="text-sm font-medium">견적을 선택해주세요</p>
                    <p className="mt-1 text-xs text-gray-300">&quot;신규&quot; 버튼으로 생성하거나 좌측에서 선택하세요.</p>
                  </div>
                </div>
              ) : !selectedReportId ? (
                <div className="flex h-full items-center justify-center text-center text-gray-400">
                  <div>
                    <FileText className="mx-auto mb-3 h-12 w-12 opacity-30" />
                    <p className="text-sm font-medium">리포트 양식을 선택해주세요</p>
                  </div>
                </div>
              ) : (
                <ReportInlineViewer
                  key={reportKey}
                  reportId={selectedReportId}
                  contextParams={contextParams}
                  onComponentClick={handleComponentClick}
                />
              )}
            </div>
          </div>
        </ResizablePanel>
      </ResizablePanelGroup>

      {/* ═══ 컴포넌트 클릭 편집 모달 (동적) ═══ */}

      {/* 텍스트/카드 편집 모달 */}
      <Dialog open={!!editComp && editComp.type !== "table"} onOpenChange={(o) => !o && setEditComp(null)}>
        <DialogContent className="max-w-lg">
          <DialogHeader>
            <DialogTitle>{getModalTitle()}</DialogTitle>
            <DialogDescription>값을 수정한 후 저장 버튼을 누르세요.</DialogDescription>
          </DialogHeader>
          <div className="flex flex-col gap-3">
            {Object.entries(editValues).map(([key, val]) => (
              <div key={key}>
                <Label className="text-xs">{key}</Label>
                {val.length > 60 ? (
                  <Textarea className="mt-1" rows={3} value={val}
                    onChange={(e) => setEditValues((p) => ({ ...p, [key]: e.target.value }))} />
                ) : (
                  <Input className="mt-1" value={val}
                    onChange={(e) => setEditValues((p) => ({ ...p, [key]: e.target.value }))} />
                )}
              </div>
            ))}
            {Object.keys(editValues).length === 0 && (
              <p className="text-sm text-gray-400">이 컴포넌트에 편집 가능한 필드가 없습니다.</p>
            )}
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setEditComp(null)}>취소</Button>
            <Button onClick={handleEditSave} disabled={saving || Object.keys(editValues).length === 0} className="gap-1.5">
              {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />} 저장
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 테이블(품목) 편집 모달 */}
      <Dialog open={!!editComp && editComp.type === "table"} onOpenChange={(o) => { if (!o && itemSearchOpen) return; if (!o) setEditComp(null); }}>
        <DialogContent className="flex max-h-[80vh] max-w-4xl flex-col overflow-hidden">
          <DialogHeader>
            <DialogTitle>견적 품목</DialogTitle>
            <DialogDescription>품목을 추가/수정합니다.</DialogDescription>
          </DialogHeader>
          <div className="flex-1 overflow-auto">
            <div className="mb-3 flex gap-2">
              <Button size="sm" className="gap-1 text-xs" onClick={() => { setItemSelectedMap(new Map()); setItemSearchResults([]); setItemSearchKeyword(""); setItemSearchOpen(true); searchItemInfo(); }}>
                <Plus className="h-3.5 w-3.5" /> 품목 검색/추가
              </Button>
              <Button variant="outline" size="sm" className="gap-1 text-xs" onClick={addItem}>
                <Plus className="h-3.5 w-3.5" /> 빈 행 추가
              </Button>
            </div>
            <div className="overflow-auto rounded border">
              <table className="w-full text-xs">
                <thead className="sticky top-0 bg-gray-50">
                  <tr>
                    <th className="w-8 px-2 py-2 text-center">#</th>
                    <th className="px-2 py-2 text-left">품목코드</th>
                    <th className="px-2 py-2 text-left">품목명</th>
                    <th className="px-2 py-2 text-left">규격</th>
                    <th className="w-20 px-2 py-2 text-right">수량</th>
                    <th className="w-16 px-2 py-2 text-center">단위</th>
                    <th className="w-24 px-2 py-2 text-right">단가</th>
                    <th className="w-24 px-2 py-2 text-right">공급가액</th>
                    <th className="w-20 px-2 py-2 text-right">부가세</th>
                    <th className="w-24 px-2 py-2 text-right">합계</th>
                    <th className="w-8" />
                  </tr>
                </thead>
                <tbody>
                  {items.map((item, idx) => (
                    <tr key={idx} className="border-t">
                      <td className="px-2 py-1 text-center text-gray-400">{idx + 1}</td>
                      <td className="px-1 py-1"><Input className="h-7 text-xs" value={item.item_code} onChange={(e) => updateItem(idx, "item_code", e.target.value)} /></td>
                      <td className="px-1 py-1"><Input className="h-7 text-xs" value={item.item_name} onChange={(e) => updateItem(idx, "item_name", e.target.value)} /></td>
                      <td className="px-1 py-1"><Input className="h-7 text-xs" value={item.spec} onChange={(e) => updateItem(idx, "spec", e.target.value)} /></td>
                      <td className="px-1 py-1"><Input className="h-7 text-right text-xs" value={fmt(item.qty)} onChange={(e) => updateItem(idx, "qty", pn(e.target.value))} /></td>
                      <td className="px-1 py-1"><Input className="h-7 text-center text-xs" value={item.unit} onChange={(e) => updateItem(idx, "unit", e.target.value)} /></td>
                      <td className="px-1 py-1"><Input className="h-7 text-right text-xs" value={fmt(item.unit_price)} onChange={(e) => updateItem(idx, "unit_price", pn(e.target.value))} /></td>
                      <td className="px-2 py-1 text-right">{fmt(item.supply_amount)}</td>
                      <td className="px-2 py-1 text-right">{fmt(item.vat_amount)}</td>
                      <td className="px-2 py-1 text-right font-medium">{fmt(item.total_amount)}</td>
                      <td className="px-1 py-1">
                        <Button variant="ghost" size="sm" className="h-6 w-6 p-0" onClick={() => removeItem(idx)}>
                          <Trash2 className="h-3 w-3 text-red-400" />
                        </Button>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <div className="mt-3 rounded bg-gray-50 p-3 text-sm">
              <div className="flex justify-between"><span>공급가액</span><span>{fmt(String(totalSupply))}원</span></div>
              <div className="flex justify-between"><span>부가세</span><span>{fmt(String(totalVat))}원</span></div>
              <div className="flex justify-between border-t pt-2 font-bold"><span>합계금액</span><span>{fmt(String(totalAmount))}원</span></div>
            </div>
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setEditComp(null)}>취소</Button>
            <Button onClick={handleEditSave} disabled={saving} className="gap-1.5">
              {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />} 저장
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ═══ 기본정보 모달 ═══ */}
      <Dialog open={basicInfoOpen} onOpenChange={setBasicInfoOpen}>
        <DialogContent className="max-w-md" onOpenAutoFocus={(e) => e.preventDefault()}>
          <DialogHeader>
            <DialogTitle>기본 정보</DialogTitle>
            <DialogDescription>견적 기본 정보를 수정합니다.</DialogDescription>
          </DialogHeader>
          <div className="grid grid-cols-2 gap-4">
            <div>
              <Label className="text-xs">견적번호</Label>
              <Input className="mt-1" value={selectedRow?.quote_no || ""} disabled />
            </div>
            <div>
              <Label className="text-xs">견적일자 *</Label>
              <div className="mt-1">
                <FormDatePicker value={basicForm.quote_date} onChange={(v) => setBasicForm((p) => ({ ...p, quote_date: v }))} />
              </div>
            </div>
            <div>
              <Label className="text-xs">유효기한 *</Label>
              <div className="mt-1">
                <FormDatePicker value={basicForm.valid_until} onChange={(v) => setBasicForm((p) => ({ ...p, valid_until: v }))} />
              </div>
            </div>
            <div>
              <Label className="text-xs">상태</Label>
              <Select value={basicForm.status} onValueChange={(v) => setBasicForm((p) => ({ ...p, status: v }))}>
                <SelectTrigger className="mt-1"><SelectValue /></SelectTrigger>
                <SelectContent>
                  {Object.entries(STATUS_MAP).map(([k, v]) => <SelectItem key={k} value={k}>{v}</SelectItem>)}
                </SelectContent>
              </Select>
            </div>
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setBasicInfoOpen(false)}>취소</Button>
            <Button onClick={handleBasicInfoSave} disabled={saving} className="gap-1.5">
              {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />} 저장
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ═══ 담당자(사원) 검색 모달 ═══ */}
      <Dialog open={userSearchOpen} onOpenChange={setUserSearchOpen}>
        <DialogContent className="flex max-h-[70vh] max-w-lg flex-col overflow-hidden">
          <DialogHeader>
            <DialogTitle>담당자 선택</DialogTitle>
            <DialogDescription>담당자를 검색하여 선택하세요.</DialogDescription>
          </DialogHeader>
          <div className="flex gap-2">
            <Input placeholder="이름 검색" value={userSearchKeyword}
              onChange={(e) => setUserSearchKeyword(e.target.value)}
              onKeyDown={(e) => e.key === "Enter" && searchUsers()} className="text-sm" />
            <Button onClick={searchUsers} disabled={userSearchLoading} className="shrink-0">
              {userSearchLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : "검색"}
            </Button>
          </div>
          <div className="flex-1 overflow-auto rounded border">
            <table className="w-full text-xs">
              <thead className="sticky top-0 bg-gray-50">
                <tr>
                  <th className="px-3 py-2 text-left">이름</th>
                  <th className="px-3 py-2 text-left">부서</th>
                  <th className="px-3 py-2 text-left">직위</th>
                  <th className="w-16 px-2 py-2 text-center">선택</th>
                </tr>
              </thead>
              <tbody>
                {userSearchResults.length === 0 ? (
                  <tr><td colSpan={4} className="px-4 py-8 text-center text-gray-400">
                    {userSearchLoading ? "검색 중..." : "검색 결과가 없습니다."}
                  </td></tr>
                ) : userSearchResults.map((row, i) => (
                  <tr key={row.objid || row.user_id || i} className="cursor-pointer border-t hover:bg-blue-50"
                    onClick={() => selectUser(row)}>
                    <td className="px-3 py-2 font-medium">{row.user_name || "-"}</td>
                    <td className="px-3 py-2">{row.department || row.dept_name || "-"}</td>
                    <td className="px-3 py-2">{row.position || row.rank || "-"}</td>
                    <td className="px-2 py-2 text-center">
                      <Button size="sm" variant="outline" className="h-6 text-xs"
                        onClick={(e) => { e.stopPropagation(); selectUser(row); }}>선택</Button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setUserSearchOpen(false)}>취소</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ═══ 거래처 검색 모달 ═══ */}
      <Dialog open={custSearchOpen} onOpenChange={setCustSearchOpen}>
        <DialogContent className="flex max-h-[80vh] max-w-2xl flex-col overflow-hidden">
          <DialogHeader>
            <DialogTitle>거래처 검색</DialogTitle>
            <DialogDescription>거래처를 검색하여 선택하세요. 선택하면 자동으로 적용됩니다.</DialogDescription>
          </DialogHeader>
          <div className="flex gap-2">
            <Input placeholder="거래처명 검색" value={custSearchKeyword}
              onChange={(e) => setCustSearchKeyword(e.target.value)}
              onKeyDown={(e) => e.key === "Enter" && searchCustomers()} className="text-sm" />
            <Button onClick={searchCustomers} disabled={custSearchLoading} className="shrink-0 gap-1">
              {custSearchLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : "검색"}
            </Button>
          </div>
          <div className="flex-1 overflow-auto rounded border">
            <table className="w-full text-xs">
              <thead className="sticky top-0 bg-gray-50">
                <tr>
                  <th className="px-3 py-2 text-left">거래처명</th>
                  <th className="px-3 py-2 text-left">대표자</th>
                  <th className="px-3 py-2 text-left">사업자번호</th>
                  <th className="px-3 py-2 text-left">연락처</th>
                  <th className="w-16 px-2 py-2 text-center">선택</th>
                </tr>
              </thead>
              <tbody>
                {custSearchResults.length === 0 ? (
                  <tr><td colSpan={5} className="px-4 py-8 text-center text-gray-400">
                    {custSearchLoading ? "검색 중..." : "검색 결과가 없습니다."}
                  </td></tr>
                ) : custSearchResults.map((row, i) => (
                  <tr key={row.objid || i} className="cursor-pointer border-t hover:bg-blue-50"
                    onClick={() => selectCustomer(row)}>
                    <td className="px-3 py-2 font-medium">{row.customer_name || "-"}</td>
                    <td className="px-3 py-2">{row.contact_person || "-"}</td>
                    <td className="px-3 py-2">{row.business_number || "-"}</td>
                    <td className="px-3 py-2">{row.contact_phone || "-"}</td>
                    <td className="px-2 py-2 text-center">
                      <Button size="sm" variant="outline" className="h-6 text-xs"
                        onClick={(e) => { e.stopPropagation(); selectCustomer(row); }}>선택</Button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <DialogFooter>
            <Button variant="outline" onClick={() => setCustSearchOpen(false)}>취소</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* ═══ 품목 검색 모달 ═══ */}
      <Dialog open={itemSearchOpen} onOpenChange={setItemSearchOpen}>
        <DialogContent className="flex max-h-[80vh] max-w-3xl flex-col overflow-hidden">
          <DialogHeader>
            <DialogTitle>품목 검색</DialogTitle>
            <DialogDescription>추가할 품목을 검색하여 선택하세요.</DialogDescription>
          </DialogHeader>
          <div className="flex gap-2">
            <Input placeholder="품명/품목코드 검색" value={itemSearchKeyword}
              onChange={(e) => setItemSearchKeyword(e.target.value)}
              onKeyDown={(e) => e.key === "Enter" && searchItemInfo()} className="text-sm" />
            <Button onClick={searchItemInfo} disabled={itemSearchLoading} className="shrink-0 gap-1">
              {itemSearchLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : "검색"}
            </Button>
          </div>
          <div className="flex-1 overflow-auto rounded border">
            <table className="w-full text-xs">
              <thead className="sticky top-0 bg-gray-50">
                <tr>
                  <th className="w-10 px-2 py-2 text-center">선택</th>
                  <th className="px-2 py-2 text-left">품목코드</th>
                  <th className="px-2 py-2 text-left">품명</th>
                  <th className="px-2 py-2 text-left">규격</th>
                  <th className="w-16 px-2 py-2 text-center">단위</th>
                  <th className="w-24 px-2 py-2 text-right">단가</th>
                </tr>
              </thead>
              <tbody>
                {itemSearchResults.length === 0 ? (
                  <tr><td colSpan={6} className="px-4 py-8 text-center text-gray-400">
                    {itemSearchLoading ? "검색 중..." : "검색 결과가 없습니다."}
                  </td></tr>
                ) : itemSearchResults.map((row, i) => {
                  const key = row.item_number || row.objid || row.id || i;
                  const checked = itemSelectedMap.has(key);
                  return (
                    <tr key={key} className={`cursor-pointer border-t ${checked ? "bg-blue-50" : "hover:bg-gray-50"}`}
                      onClick={() => toggleItemSelect(row)}>
                      <td className="px-2 py-1.5 text-center">
                        <input type="checkbox" checked={checked} readOnly className="accent-blue-500" />
                      </td>
                      <td className="px-2 py-1.5">{row.item_number || row.item_code || "-"}</td>
                      <td className="px-2 py-1.5">{row.item_name || "-"}</td>
                      <td className="px-2 py-1.5">{row.spec || row.standard || "-"}</td>
                      <td className="px-2 py-1.5 text-center">{row.unit || "EA"}</td>
                      <td className="px-2 py-1.5 text-right">{fmt(String(row.selling_price || row.standard_price || 0))}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
          <DialogFooter className="flex items-center justify-between">
            <span className="text-xs text-gray-500">선택: {itemSelectedMap.size}건</span>
            <div className="flex gap-2">
              <Button variant="outline" onClick={() => setItemSearchOpen(false)}>취소</Button>
              <Button onClick={addSelectedItemsToQuote} disabled={itemSelectedMap.size === 0} className="gap-1">
                <Plus className="h-4 w-4" /> 선택 품목 추가
              </Button>
            </div>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      <ExcelUploadModal open={excelOpen} onOpenChange={setExcelOpen} tableName={MASTER_TABLE} />
      {ConfirmDialogComponent}
    </div>
  );
}
