---
import specs from "blume:openapi";
import {
  mergeParameters,
  type ParameterLike,
  resolveComponentRef,
  type SchemaLike,
} from "./helpers.ts";
import { operationModel } from "./operation-model.ts";
import { buildRequest, defaultValues } from "./request.ts";
import {
  effectiveSecurity,
  resolveSecurity,
  type SecurityRequirementLike,
  type SecuritySchemeLike,
} from "./security.ts";
import { sampleLanguages } from "./snippets.ts";
import AsyncApiOperation from "./AsyncApiOperation.astro";
import Authorization from "./Authorization.astro";
import MethodBadge from "./MethodBadge.astro";
import ParametersTable from "./ParametersTable.astro";
import Playground from "./Playground.astro";
import RequestBody from "./RequestBody.astro";
import RequestPanel from "./RequestPanel.astro";
import Responses from "./Responses.astro";

interface Props {
  source: string;
  id: string;
}

interface MediaTypeLike {
  schema?: SchemaLike;
  example?: unknown;
}

interface RequestBodyLike {
  $ref?: string;
  description?: string;
  required?: boolean;
  content?: Record<string, MediaTypeLike>;
}

interface ResponseLike {
  $ref?: string;
  description?: string;
  content?: Record<string, MediaTypeLike>;
}

interface FullOperation {
  summary?: string;
  description?: string;
  deprecated?: boolean;
  parameters?: ParameterLike[];
  requestBody?: RequestBodyLike;
  responses?: Record<string, ResponseLike>;
  security?: SecurityRequirementLike[];
}

const { source, id } = Astro.props;
const spec = specs[source];
const ref = spec?.operations[id];
// The AsyncAPI front-end renders its own body; the lookups below are
// OpenAPI-shaped (paths, request/response) and resolve to nothing for it.
const isAsyncApi = spec?.kind === "asyncapi";

const doc = (spec?.document ?? {}) as {
  paths?: Record<
    string,
    Record<string, unknown> & { parameters?: ParameterLike[] }
  >;
  components?: {
    schemas?: Record<string, SchemaLike>;
    parameters?: Record<string, ParameterLike>;
    requestBodies?: Record<string, RequestBodyLike>;
    responses?: Record<string, ResponseLike>;
    securitySchemes?: Record<string, SecuritySchemeLike>;
  };
  security?: SecurityRequirementLike[];
  servers?: { url?: string }[];
};
const pathItem = ref ? doc.paths?.[ref.path] : undefined;
const operation = (
  pathItem && ref ? pathItem[ref.method] : undefined
) as FullOperation | undefined;

const schemas = doc.components?.schemas ?? {};
const components = doc.components ?? {};
const params = mergeParameters(
  pathItem?.parameters,
  operation?.parameters,
  components
);
const requestBody = operation?.requestBody
  ? resolveComponentRef(operation.requestBody, components, "requestBodies")
  : undefined;
const responses = Object.fromEntries(
  Object.entries(operation?.responses ?? {}).map(([status, response]) => [
    status,
    resolveComponentRef(response, components, "responses"),
  ])
);

const security = resolveSecurity(
  effectiveSecurity(operation?.security, doc.security),
  doc.components?.securitySchemes
);

// One derived model feeds the static default sample, the playground form,
// and (client-side) the live samples + fetch — they can never drift apart.
const model =
  ref && operation
    ? operationModel({
        method: ref.method,
        parameters: params,
        path: ref.path,
        requestBody,
        schemas,
        security,
        servers: doc.servers ?? [],
      })
    : null;
const sample = model ? buildRequest(model, defaultValues(model)) : null;
const languages = sampleLanguages(spec?.codeSamples ?? []);
---
{
  isAsyncApi ? (
    <AsyncApiOperation id={id} source={source} />
  ) : !(spec && ref && operation) ? (
    <div class="text-muted-foreground">This API operation could not be found.</div>
  ) : (
    <div class="not-prose">
      <div class="mb-6 flex flex-wrap items-center gap-3">
        <MethodBadge method={ref.method} />
        <code class="break-all font-mono text-foreground text-sm">
          {ref.path}
        </code>
        {operation.deprecated && (
          <span class="font-medium text-[0.625rem] text-orange-600 uppercase tracking-wide dark:text-orange-400">
            deprecated
          </span>
        )}
      </div>
      <div class="grid grid-cols-1 items-start gap-x-10 gap-y-8 xl:grid-cols-[minmax(0,1fr)_minmax(0,28rem)]">
        <div>
          <Authorization security={security} />
          <ParametersTable parameters={params} schemas={schemas} />
          {requestBody && (
            <RequestBody
              expandAll={spec.expandSchemas}
              requestBody={requestBody}
              schemas={schemas}
            />
          )}
          {operation.responses && (
            <Responses
              expandAll={spec.expandSchemas}
              responses={responses}
              schemas={schemas}
            />
          )}
        </div>
        {sample && model && (
          <div class="xl:sticky xl:top-24 xl:self-start" data-operation-panel>
            {spec.playground.enabled && (
              <Playground
                model={model}
                operation={id}
                proxy={spec.playground.proxy}
                slug={spec.slug}
              />
            )}
            <RequestPanel
              languages={languages}
              responses={responses}
              sample={sample}
              schemas={schemas}
            />
          </div>
        )}
      </div>
    </div>
  )
}
