import { Schema } from "effect";
import { Expression } from "./expression.js";
const TrackCount = Schema.Number.pipe(Schema.int(), Schema.positive());
const GridCoord = Schema.Number.pipe(Schema.int(), Schema.positive());
export const Corner = Schema.Tuple(GridCoord, GridCoord).annotations({
  title: "Corner",
  description: "[x, y] \u7F51\u683C\u7EBF\u53F7\uFF081 \u57FA\uFF09"
});
export const Area = Schema.Tuple(Corner, Corner).pipe(Schema.filter((a) => {
  if (a[1][0] <= a[0][0]) return "\u53F3\u4E0B\u5217\u7EBF\u5FC5\u987B\u5927\u4E8E\u5DE6\u4E0A\u5217\u7EBF";
  if (a[1][1] <= a[0][1]) return "\u53F3\u4E0B\u884C\u7EBF\u5FC5\u987B\u5927\u4E8E\u5DE6\u4E0A\u884C\u7EBF";
  return true;
})).annotations({
  title: "Area",
  description: "\u77E9\u5F62\u533A\u57DF\uFF1A`[[x1, y1], [x2, y2]]`\uFF0C\u5DE6\u4E0A + \u53F3\u4E0B\u5BF9\u89D2\uFF08\u7EC8\u6B62\u7EBF\u4E0D\u542B\uFF09"
});
export const Align = Schema.Literal("start", "center", "end", "stretch").annotations({
  title: "Align",
  description: "\u7F51\u683C\u9879\u5728\u5355\u8F74\u4E0A\u7684\u5BF9\u9F50\u65B9\u5F0F\uFF1B\u7F3A\u7701\u7B49\u540C\u4E8E stretch"
});
export const Placement = Schema.Struct({
  area: Area,
  h: Schema.optional(Align.annotations({ title: "\u6C34\u5E73\u5BF9\u9F50", description: "\u6C34\u5E73\u8F74\u5BF9\u9F50\uFF08justify-self\uFF09" })),
  v: Schema.optional(Align.annotations({ title: "\u5782\u76F4\u5BF9\u9F50", description: "\u5782\u76F4\u8F74\u5BF9\u9F50\uFF08align-self\uFF09" }))
}).annotations({
  title: "Placement",
  description: "\u5757\u5728\u67D0\u4E00\u5E03\u5C40\u4E2D\u7684\u4F4D\u7F6E\u4E0E\u5BF9\u9F50\u3002`area` \u7684\u5BBD `(x2-x1)` \u4E0E\u9AD8 `(y2-y1)` \u63A8\u8350\u53D6 `search_metadata` \u8FD4\u56DE\u7684 `w.initial` / `h.initial`\uFF1B\u53EF\u5728 `[w.min, w.max]` / `[h.min, h.max]` \u5185\u81EA\u884C\u8C03\u8282\u4EE5\u6539\u53D8\u5B9E\u9645\u5360\u4F4D\uFF08\u66F4\u5927\u7684\u503C\u5373\u5360\u66F4\u591A\u7684\u7F51\u683C\u5217/\u884C\uFF0C\u4ECE\u800C\u62FF\u5230\u66F4\u5927\u7684\u6E32\u67D3\u6BD4\u4F8B\uFF09"
});
export const DEFAULT_GAP = 4;
export const Layout = Schema.Struct({
  columns: TrackCount.annotations({
    title: "\u5217\u6570",
    description: "\u603B\u5217\u6570\u3002\u6BCF\u5217\u7B49\u5BBD\uFF0C\u6E32\u67D3\u65F6\u5C55\u5F00\u4E3A `repeat(N, minmax(0, 1fr))` \u2014 \u5217\u4F1A\u6536\u7F29\u4EE5\u9002\u914D\u5BB9\u5668\u5BBD\u5EA6"
  }),
  rows: Schema.optional(TrackCount.annotations({
    title: "\u884C\u6570",
    description: "\u603B\u884C\u6570\u3002\u6BCF\u884C\u7B49\u6743\uFF0C\u6E32\u67D3\u65F6\u5C55\u5F00\u4E3A `repeat(N, minmax(0, 1fr))` \u2014 \u5728\u81EA\u7136\u9AD8\u5EA6\u5BB9\u5668\u5185\u7B49\u540C\u4E8E `auto`"
  })),
  gap: Schema.optional(Schema.Number.pipe(Schema.int(), Schema.nonNegative()).annotations({
    title: "\u7F51\u683C\u95F4\u8DDD",
    description: "\u975E\u8D1F\u6574\u6570\uFF1B\u5B9E\u9645 CSS gap \u4E3A `calc(n * 0.25rem)`\u3002\u663E\u5F0F `0` \u8868\u793A\u65E0\u95F4\u8DDD\uFF0C\u672A\u8BBE\u7F6E\u5219\u53D6\u9ED8\u8BA4\u95F4\u8DDD 4\uFF08\u5373 `1rem`\uFF09"
  })),
  style: Schema.optional(Schema.String.annotations({
    title: "\u5BB9\u5668\u6837\u5F0F",
    description: "\u5BB9\u5668\u7684\u5185\u8054\u6837\u5F0F\u5B57\u7B26\u4E32"
  })),
  placements: Schema.Record({
    key: Schema.String,
    value: Placement
  }).annotations({
    title: "\u4F4D\u7F6E",
    description: "\u4EE5\u5757 `id` \u4E3A\u952E\u7684\u4F4D\u7F6E\u6620\u5C04\uFF1B\u672A\u6620\u5C04\u7684\u5757\u5728\u8BE5\u5E03\u5C40\u4E2D\u4E0D\u6E32\u67D3"
  })
}).annotations({
  title: "Layout",
  description: "\u4E00\u5957\u5B8C\u6574\u7684\u7F51\u683C\u5E03\u5C40"
});
function configureMedia(configure) {
  return (env) => {
    env.registerFunction(
      "media(string): bool",
      (_self, q) => typeof window !== "undefined" && window.matchMedia(q).matches,
      { description: "\u5339\u914D CSS \u5A92\u4F53\u67E5\u8BE2\uFF0C\u7B49\u4EF7\u4E8E `window.matchMedia(q).matches`\uFF1BSSR \u4E0A\u4E0B\u6587\u8FD4\u56DE `false`" }
    );
    configure(env);
  };
}
export function LayoutSet(configure) {
  return Schema.Struct({
    name: Schema.String.annotations({
      title: "\u540D\u79F0",
      description: "\u7F16\u8F91\u5668\u4E2D\u533A\u5206\u4E0D\u540C\u5E03\u5C40\u7684\u5185\u90E8\u540D\u79F0"
    }),
    media: Schema.optional(Expression({
      configure: configureMedia(configure),
      resultType: "bool"
    }).annotations({
      title: "\u5339\u914D\u6761\u4EF6",
      description: "\u8FD4\u56DE `bool` \u7684 CEL \u8868\u8FBE\u5F0F\uFF0C\u6302\u8F7D\u65F6\u4E00\u6B21\u6027\u6C42\u503C\uFF1B\u7A7A\u8868\u793A\u65E0\u6761\u4EF6\u5339\u914D\u3002\u53EF\u7528 `media('(min-width: 1024px)')` \u5305\u88C5 CSS \u5A92\u4F53\u67E5\u8BE2\u3002"
    })),
    layout: Layout
  }).annotations({
    title: "LayoutSet",
    description: "\u5E26\u5339\u914D\u6761\u4EF6\u7684\u5E03\u5C40\u96C6\u5408\u9879"
  });
}
export function validatePlacements(items, layouts, label) {
  const ids = new Set(items.map((it) => it.id));
  for (const [i, set] of layouts.entries()) {
    const nCols = set.layout.columns;
    const nRows = set.layout.rows;
    for (const [id, placement] of Object.entries(set.layout.placements)) {
      if (!ids.has(id)) {
        return `\u5E03\u5C40 #${i} (${set.name}) \u5F15\u7528\u4E86\u4E0D\u5B58\u5728\u7684${label} id: ${id}`;
      }
      const [, [x2, y2]] = placement.area;
      if (x2 > nCols + 1) {
        return `\u5E03\u5C40 #${i} (${set.name}) ${label} ${id} \u7684\u5217\u533A\u57DF\u8D85\u51FA\u5217\u5B9A\u4E49\uFF08\u5171 ${nCols} \u5217\uFF09`;
      }
      if (nRows !== void 0 && y2 > nRows + 1) {
        return `\u5E03\u5C40 #${i} (${set.name}) ${label} ${id} \u7684\u884C\u533A\u57DF\u8D85\u51FA\u884C\u5B9A\u4E49\uFF08\u5171 ${nRows} \u884C\uFF09`;
      }
    }
  }
  return true;
}
export function normalizeLayoutSet(ls) {
  const entries = Object.entries(ls.layout.placements);
  if (entries.length === 0) {
    return {
      ...ls,
      layout: { ...ls.layout, columns: 1, ...ls.layout.rows !== void 0 ? { rows: 1 } : {} }
    };
  }
  let minX = Infinity;
  let minY = Infinity;
  for (const [, p] of entries) {
    if (p.area[0][0] < minX) minX = p.area[0][0];
    if (p.area[0][1] < minY) minY = p.area[0][1];
  }
  const sx = minX !== 1 ? 1 - minX : 0;
  const sy = minY !== 1 ? 1 - minY : 0;
  const placements = sx || sy ? Object.fromEntries(entries.map(([id, p]) => {
    const [[ax, ay], [bx, by]] = p.area;
    return [id, { ...p, area: [[ax + sx, ay + sy], [bx + sx, by + sy]] }];
  })) : Object.fromEntries(entries);
  let cols = 0;
  let rows = 0;
  for (const p of Object.values(placements)) {
    const [, [x2, y2]] = p.area;
    if (x2 - 1 > cols) cols = x2 - 1;
    if (y2 - 1 > rows) rows = y2 - 1;
  }
  return {
    ...ls,
    layout: {
      ...ls.layout,
      placements,
      columns: Math.max(1, cols),
      ...ls.layout.rows !== void 0 ? { rows: Math.max(1, rows) } : {}
    }
  };
}
export function findFreePlacement(placements, w, h) {
  const ww = Math.max(1, w);
  const hh = Math.max(1, h);
  const others = Object.values(placements).map((p) => p.area);
  const overlaps = (a, b) => a[0][0] < b[1][0] && a[1][0] > b[0][0] && a[0][1] < b[1][1] && a[1][1] > b[0][1];
  for (let y = 1; y < 1e3; y++) {
    const area = [[1, y], [1 + ww, y + hh]];
    if (!others.some((o) => overlaps(area, o))) return { area };
  }
  return { area: [[1, 1], [1 + ww, 1 + hh]] };
}
export function placeGroupAt(existing, group, target) {
  const entries = Object.entries(group);
  if (entries.length === 0) return {};
  let gx1 = Infinity;
  let gy1 = Infinity;
  let gx2 = -Infinity;
  let gy2 = -Infinity;
  for (const [, p] of entries) {
    const [[ax, ay], [bx, by]] = p.area;
    if (ax < gx1) gx1 = ax;
    if (ay < gy1) gy1 = ay;
    if (bx > gx2) gx2 = bx;
    if (by > gy2) gy2 = by;
  }
  const gw = gx2 - gx1;
  const gh = gy2 - gy1;
  const tx = Math.round(target.x);
  const ty0 = Math.round(target.y);
  const others = Object.values(existing).map((p) => p.area);
  const overlaps = (a, b) => a[0][0] < b[1][0] && a[1][0] > b[0][0] && a[0][1] < b[1][1] && a[1][1] > b[0][1];
  let ty = ty0;
  for (let y = ty0; y < ty0 + 1e3; y++) {
    const box = [[tx, y], [tx + gw, y + gh]];
    if (!others.some((o) => overlaps(box, o))) {
      ty = y;
      break;
    }
  }
  const sx = tx - gx1;
  const sy = ty - gy1;
  return Object.fromEntries(entries.map(([id, p]) => {
    const [[ax, ay], [bx, by]] = p.area;
    return [id, { ...p, area: [[ax + sx, ay + sy], [bx + sx, by + sy]] }];
  }));
}
export function Slot(BlockSchemaUnion, configure) {
  return Schema.Struct({
    blocks: Schema.Array(BlockSchemaUnion).annotations({
      title: "\u5757",
      description: "\u5757\u5217\u8868\uFF1B\u672A\u7ED1\u5B9A\u5230\u4EFB\u4F55\u5E03\u5C40\u7684\u5757\u4E0D\u4F1A\u88AB\u6E32\u67D3"
    }),
    layouts: Schema.Array(LayoutSet(configure)).pipe(Schema.minItems(1)).annotations({
      title: "\u5E03\u5C40",
      description: "\u6309\u987A\u5E8F\u6392\u5217\u7684\u5E03\u5C40\u96C6\u5408\uFF1B\u6302\u8F7D\u65F6\u6309\u5E8F\u6C42\u503C\u5404\u9879 `media` CEL \u8868\u8FBE\u5F0F\uFF0C\u4F7F\u7528\u7B2C\u4E00\u4E2A\u4E3A `true` \u7684\u5E03\u5C40\uFF08\u5168\u90E8\u4E0D\u5339\u914D\u65F6\u56DE\u9000\u5230\u6700\u540E\u4E00\u9879\uFF09"
    })
  }).pipe(Schema.filter((slot) => {
    const items = slot.blocks.map((b) => ({ id: b.id }));
    return validatePlacements(items, slot.layouts, "\u5757");
  })).annotations({
    title: "Slot",
    description: "\u5757\u4E0E\u5E03\u5C40\u7684\u7EC4\u5408\uFF1A\u5B9A\u4E49\u4E00\u7EC4\u5757\u4EE5\u53CA\u5B83\u4EEC\u5728\u4E0D\u540C\u5E03\u5C40\u4E0B\u7684\u4F4D\u7F6E"
  });
}
export { Expression };
export function defaultSlot() {
  return {
    blocks: [],
    layouts: [{ name: "\u9ED8\u8BA4", layout: { columns: 1, placements: {} } }]
  };
}
function freshId() {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
    const r = Math.random() * 16 | 0;
    return (c === "x" ? r : r & 3 | 8).toString(16);
  });
}
function isSlotShape(v) {
  if (v === null || typeof v !== "object") return false;
  const o = v;
  return Array.isArray(o.blocks) && Array.isArray(o.layouts);
}
function rewriteNestedSlots(v) {
  if (v === null || typeof v !== "object") return v;
  if (Array.isArray(v)) return v.map(rewriteNestedSlots);
  if (isSlotShape(v)) return regenSlotIds(v);
  const out = {};
  for (const [k, val] of Object.entries(v)) {
    out[k] = rewriteNestedSlots(val);
  }
  return out;
}
export function regenSlotIds(slot) {
  const idMap = /* @__PURE__ */ new Map();
  const newBlocks = slot.blocks.map((b) => {
    const newId = freshId();
    idMap.set(b.id, newId);
    const rewritten = rewriteNestedSlots(b);
    return { ...rewritten, id: newId };
  });
  const newLayouts = slot.layouts.map((ls) => ({
    ...ls,
    layout: {
      ...ls.layout,
      placements: Object.fromEntries(
        Object.entries(ls.layout.placements).map(([id, p]) => [idMap.get(id) ?? id, p])
      )
    }
  }));
  return { blocks: newBlocks, layouts: newLayouts };
}
