/**
 * `sdk/server` — the HOST side of the app backend, imported normally by the runner
 * (packages/lecodes-runner), the backend and tests. Nothing here is bundled into project code; the
 * bundle carries its own SDK copy and meets this module only through the `globalThis.__lecodes*`
 * seams (see ./runtime.ts).
 */

export {
  loadServerModules, createInvoker, createJoinGuard, setChannelPublisher, setRequestProvider,
} from "./runtime"
export type { LoadedServer, InvokeResult, InvokerOptions, ServerRegistration } from "./runtime"
export type { RequestContext } from "./context"
export { setDbTransport } from "./db/defineDb"
export { marciHttpTransport, marciRequest, encodeMarciId, MarciHttpError } from "./db/httpTransport"
export type { MarciHttpOptions } from "./db/httpTransport"
export type { MarciOp, MarciTransport } from "./db/marci/query"
export type { ServerManifest, ServerExport } from "../compile/serverSplit"
export { validateArgs, ValidationError } from "./validate"
export type { ParamSchema, Schema as ParamSchemaNode } from "./validate"
export { createAuthHost, signNonce, verifyNonce, sha256 } from "./auth/host"
export type { AuthHost, AuthHostConfig, AuthMail } from "./auth/host"
export type { AuthUser, AuthState, AuthOps, OAuthProfile, OAuthCredential } from "./auth/types"
export type { AuthProvider, AppConfig } from "./auth/appConfig"

import type { LoadedServer } from "./runtime"
import type { Field, FieldDef } from "./db/fields"
import type { Model } from "./db/types"

/** JSON view of one field for the platform (Data tab, docs): `FieldDef` with nested struct fields flattened
 *  to defs and functions/undefineds dropped. */
export type FieldDescription = Omit<FieldDef, "fields" | "default"> & { fields?: Record<string, FieldDescription>, default?: unknown }

/** What the runner reports to the platform after loading a bundle: schema text + models + the
 *  endpoint/channel lists (with the manifest's param schemas). */
export type ServerDescription = {
  /** `.marci` schema text ("" when the project defines no db). */
  marci: string
  models: Record<string, { fields: Record<string, FieldDescription> }>
  endpoints: { id: string, params: unknown[] | null }[]
  channels: string[]
  /** Which models the auth runtime binds to (`null` = the project has no db) and the app.json providers. */
  auth: { user: string | null, session: string | null, providers: string[] }
  /** `app.json` name (when the compile embedded one). */
  appName?: string
}

const describeField = (f: Field<any, any>): FieldDescription => {
  const def = f.def
  const out: FieldDescription = { ...def, fields: undefined, default: undefined }
  delete out.fields
  delete out.default
  if (def.hasDefault) out.default = def.default === undefined ? null : def.default
  if (def.fields) out.fields = Object.fromEntries(Object.entries(def.fields).map(([k, v]) => [k, describeField(v)]))
  return JSON.parse(JSON.stringify(out))
}

export const describeServer = (server: LoadedServer): ServerDescription => {
  const db = server.dbs[0]
  const models: ServerDescription["models"] = {}
  if (db) {
    for (const [name, m] of Object.entries(db.$models as Record<string, Model>)) {
      models[name] = { fields: Object.fromEntries(Object.entries(m.fields as Record<string, Field<any, any>>).map(([k, f]) => [k, describeField(f)])) }
    }
  }
  const params = (server.manifest.params ?? {}) as Record<string, unknown[]>
  return {
    marci: db?.$schema ?? "",
    models,
    endpoints: [...server.endpoints.keys()].map(id => ({ id, params: params[id] ?? null })),
    channels: [...server.channels.keys()],
    auth: { user: db?.$auth.user ?? null, session: db?.$auth.session ?? null, providers: server.manifest.app?.auth?.providers ?? [] },
    appName: server.manifest.app?.name,
  }
}
