import { defineAsyncComponent } from "vue";
import { Effect, Either } from "effect";
import { toast } from "vue-sonner";
import { useOverlay } from "../../../composables/useOverlay.js";
import { defineRegistry } from "../../../share/define-registry.js";
import { dispatchTriggers } from "../../../share/event-bus.js";
const sourceKey = (type, compatibilityDate) => `${type}@${compatibilityDate}`;
const schemaModules = import.meta.glob(
  "../buttons/*/*/schema.{ts,js}",
  { eager: true, exhaustive: true }
);
const configModuleLoaders = import.meta.glob(
  "../buttons/*/*/config.vue",
  { exhaustive: true }
);
const runtimeModuleLoaders = import.meta.glob(
  "../buttons/*/*/runtime.vue",
  { exhaustive: true }
);
const configModules = Object.fromEntries(
  Object.entries(configModuleLoaders).map(([path, loader]) => [
    path,
    defineAsyncComponent(loader)
  ])
);
const runtimeModules = Object.fromEntries(
  Object.entries(runtimeModuleLoaders).map(([path, loader]) => [
    path,
    defineAsyncComponent(loader)
  ])
);
const registry = defineRegistry({
  hostName: "shwfed-actions",
  dirSegment: "buttons",
  schemaModules,
  configModules,
  runtimeModules,
  // A button is deprecated by one of three rules, in precedence order:
  //   (1) another entry declares this one in its `migrateFrom` — explicit
  //       cross-type supersession with a structured `supersededBy` target the
  //       inspector can offer a one-click 「迁移」 against;
  //   (2) its schema declares a free-text `metadata.deprecated` note — the
  //       escape hatch for a replacement with no structured migrate target;
  //   (3) a newer `compatibilityDate` of the same `type` exists — the
  //       date-versioned rule shared with form / table.
  // `migrateFrom` wins over the date rule: a kept-around stub of an old type
  // points at exactly which target it migrates to, so trust it over the
  // implicit "any older same-type date is the same component upgraded" guess.
  deriveEntryExtras: (drafts) => {
    const latestByType = /* @__PURE__ */ new Map();
    for (const d of drafts) {
      const prev = latestByType.get(d.type);
      if (!prev || d.compatibilityDate > prev) latestByType.set(d.type, d.compatibilityDate);
    }
    const migrationTargets = /* @__PURE__ */ new Map();
    for (const d of drafts) {
      const sources = d.module.migrateFrom;
      if (!sources || sources.length === 0) continue;
      for (const src of sources) {
        const key = sourceKey(src.type, src.compatibilityDate);
        const existing = migrationTargets.get(key);
        if (existing) {
          throw new Error(
            `[shwfed-actions] migrateFrom collision: source "${key}" is claimed by both "${sourceKey(existing.type, existing.compatibilityDate)}" and "${sourceKey(d.type, d.compatibilityDate)}"`
          );
        }
        migrationTargets.set(key, { type: d.type, compatibilityDate: d.compatibilityDate });
      }
    }
    return (draft) => {
      const declared = migrationTargets.get(sourceKey(draft.type, draft.compatibilityDate));
      if (declared) {
        return {
          deprecated: true,
          supersededBy: declared,
          deprecationNote: declared.type === draft.type ? `\u5DF2\u88AB\u66F4\u65B0\u7248\u672C ${declared.compatibilityDate} \u53D6\u4EE3\u3002` : `\u5DF2\u88AB ${declared.type}@${declared.compatibilityDate} \u53D6\u4EE3\u3002`
        };
      }
      const note = draft.module.metadata.deprecated;
      if (note) return { deprecated: true, deprecationNote: note };
      const latest = latestByType.get(draft.type);
      if (draft.compatibilityDate < latest) {
        return {
          deprecated: true,
          supersededBy: { type: draft.type, compatibilityDate: latest },
          deprecationNote: `\u5DF2\u88AB\u66F4\u65B0\u7248\u672C ${latest} \u53D6\u4EE3\u3002`
        };
      }
      return { deprecated: false };
    };
  }
});
export const BUTTONS = registry.ENTRIES.map((e) => ({
  type: e.type,
  compatibilityDate: e.compatibilityDate,
  metadata: e.module.metadata,
  schema: e.module.schema,
  config: e.config,
  runtime: e.runtime,
  deprecated: e.deprecated,
  ...e.deprecationNote !== void 0 ? { deprecationNote: e.deprecationNote } : {},
  ...e.supersededBy !== void 0 ? { supersededBy: e.supersededBy } : {},
  migrate: e.module.migrate,
  ...e.module.migrateFrom !== void 0 ? { migrateFrom: e.module.migrateFrom } : {}
}));
let _migrationTargets = null;
function getMigrationTargets() {
  if (_migrationTargets) return _migrationTargets;
  const map = /* @__PURE__ */ new Map();
  for (const entry of BUTTONS) {
    if (!entry.migrateFrom) continue;
    for (const src of entry.migrateFrom) {
      map.set(sourceKey(src.type, src.compatibilityDate), {
        type: entry.type,
        compatibilityDate: entry.compatibilityDate
      });
    }
  }
  _migrationTargets = map;
  return map;
}
export function findMigrationTarget(type, compatibilityDate) {
  const target = getMigrationTargets().get(sourceKey(type, compatibilityDate));
  if (!target) return void 0;
  return findButton(target.type, target.compatibilityDate);
}
export function allButtonSchemas(configure) {
  return BUTTONS.map((entry) => entry.schema(configure).annotations({
    identifier: `${entry.type}@${entry.compatibilityDate}`
  }));
}
export function findButton(type, compatibilityDate) {
  return BUTTONS.find((a) => a.type === type && a.compatibilityDate === compatibilityDate);
}
const itemSchemaModules = import.meta.glob(
  "../items/*/*/schema.{ts,js}",
  { eager: true, exhaustive: true }
);
const itemConfigModuleLoaders = import.meta.glob(
  "../items/*/*/config.vue",
  { exhaustive: true }
);
const itemRuntimeModuleLoaders = import.meta.glob(
  "../items/*/*/runtime.vue",
  { exhaustive: true }
);
const itemConfigModules = Object.fromEntries(
  Object.entries(itemConfigModuleLoaders).map(([path, loader]) => [
    path,
    defineAsyncComponent(loader)
  ])
);
const itemRuntimeModules = Object.fromEntries(
  Object.entries(itemRuntimeModuleLoaders).map(([path, loader]) => [
    path,
    defineAsyncComponent(loader)
  ])
);
const itemRegistry = defineRegistry({
  hostName: "shwfed-actions",
  dirSegment: "items",
  schemaModules: itemSchemaModules,
  configModules: itemConfigModules,
  runtimeModules: itemRuntimeModules
});
export const ITEMS = itemRegistry.ENTRIES.map((e) => ({
  type: e.type,
  compatibilityDate: e.compatibilityDate,
  metadata: e.module.metadata,
  schema: e.module.schema,
  config: e.config,
  runtime: e.runtime
}));
export function allItemSchemas(configure) {
  return ITEMS.map((entry) => entry.schema(configure).annotations({
    identifier: `${entry.type}@${entry.compatibilityDate}`
  }));
}
export function findItem(type, compatibilityDate) {
  return ITEMS.find((i) => i.type === type && i.compatibilityDate === compatibilityDate);
}
const INTERPOLATION_RE = /\{\{(.*?)\}\}/gs;
export function interpolateMarkdown(source, $cel, context) {
  return source.replace(INTERPOLATION_RE, (_, expr) => {
    const trimmed = expr.trim();
    if (trimmed.length === 0) return "{{ }}";
    try {
      return stringifyCelResult(Effect.runSync($cel(trimmed, context)));
    } catch {
      return `{{${expr}}}`;
    }
  });
}
function stringifyCelResult(value) {
  if (value === null || value === void 0) return "";
  if (typeof value === "object") return JSON.stringify(value);
  return String(value);
}
const TOAST_STYLES = /* @__PURE__ */ new Set(["success", "error", "warning", "info"]);
function classifyAndDispatch($cel, context, outcome) {
  const { messageExpression, resultExpression, channel, triggers } = outcome;
  const result = resultExpression === void 0 ? Effect.succeed("success") : Effect.map($cel(resultExpression, context), (s) => TOAST_STYLES.has(s) ? s : "success");
  return Effect.flatMap(result, (kind) => {
    const toasted = messageExpression === void 0 ? Effect.void : Effect.flatMap(
      $cel(messageExpression, context),
      (message) => Effect.sync(() => toast[kind](message))
    );
    return Effect.andThen(toasted, dispatchTriggers(channel, triggers?.[kind], $cel));
  });
}
export function executeHttpRequest(expression, $cel, outcome = {}) {
  const request = Effect.flatMap(
    $cel(expression),
    (builder) => builder.json()
  );
  return Effect.flatMap(request, (body) => classifyAndDispatch($cel, { json: body }, outcome));
}
export function executeHttpRequestBatch(expression, $cel, outcome = {}) {
  const { continueOnError = false, markdown, messageExpression, resultExpression } = outcome;
  return Effect.gen(function* () {
    const builders = yield* $cel(expression);
    const total = builders.length;
    let succeeded = 0;
    let failure = 0;
    const hasMarkdown = markdown !== void 0 && markdown.length > 0;
    const render = () => hasMarkdown ? interpolateMarkdown(markdown, $cel, { total, succeeded, failure }) : "";
    const handle = yield* useOverlay().openProgress({ total, cancellable: true, content: render() });
    const jsons = [];
    yield* Effect.ensuring(
      Effect.gen(function* () {
        for (const builder of builders) {
          if (yield* handle.isCancelled) break;
          const response = yield* Effect.either(builder.json());
          let ok = false;
          if (Either.isRight(response)) {
            const json = response.right;
            ok = resultExpression === void 0 ? true : yield* $cel(resultExpression, { json });
            if (ok) {
              jsons.push(json);
              succeeded += 1;
            } else {
              failure += 1;
            }
          } else {
            failure += 1;
          }
          if (!ok) yield* handle.markFailed;
          yield* handle.advance;
          if (hasMarkdown) yield* handle.setContent(render());
          if (!ok && !continueOnError) break;
        }
      }),
      handle.finish
    );
    if (messageExpression !== void 0) {
      const message = yield* $cel(messageExpression, { jsons });
      yield* Effect.sync(() => toast[failure === 0 ? "success" : "error"](message));
    }
  });
}
