#!/usr/bin/env node
/**
 * One-off script to generate operation files for the 37 entities that have no operations.
 * Run from repo root: node packages/@openhi/constructs/scripts/generate-operations.js
 */

const fs = require("fs");
const path = require("path");

const ENTITIES = [
  { key: "capabilitystatement", type: "CapabilityStatement" },
  { key: "codesystem", type: "CodeSystem" },
  { key: "compartmentdefinition", type: "CompartmentDefinition" },
  { key: "conceptmap", type: "ConceptMap" },
  { key: "examplescenario", type: "ExampleScenario" },
  { key: "graphdefinition", type: "GraphDefinition" },
  { key: "implementationguide", type: "ImplementationGuide" },
  { key: "medicinalproduct", type: "MedicinalProduct" },
  { key: "medicinalproductauthorization", type: "MedicinalProductAuthorization" },
  { key: "medicinalproductcontraindication", type: "MedicinalProductContraindication" },
  { key: "medicinalproductingredient", type: "MedicinalProductIngredient" },
  { key: "medicinalproductindication", type: "MedicinalProductIndication" },
  { key: "medicinalproductinteraction", type: "MedicinalProductInteraction" },
  { key: "medicinalproductmanufactured", type: "MedicinalProductManufactured" },
  { key: "medicinalproductpackaged", type: "MedicinalProductPackaged" },
  { key: "medicinalproductpharmaceutical", type: "MedicinalProductPharmaceutical" },
  { key: "medicinalproductundesirableeffect", type: "MedicinalProductUndesirableEffect" },
  { key: "messagedefinition", type: "MessageDefinition" },
  { key: "namingsystem", type: "NamingSystem" },
  { key: "observationdefinition", type: "ObservationDefinition" },
  { key: "operationdefinition", type: "OperationDefinition" },
  { key: "researchdefinition", type: "ResearchDefinition" },
  { key: "researchelementdefinition", type: "ResearchElementDefinition" },
  { key: "searchparameter", type: "SearchParameter" },
  { key: "specimendefinition", type: "SpecimenDefinition" },
  { key: "structuredefinition", type: "StructureDefinition" },
  { key: "structuremap", type: "StructureMap" },
  { key: "substancenucleicacid", type: "SubstanceNucleicAcid" },
  { key: "substancepolymer", type: "SubstancePolymer" },
  { key: "substanceprotein", type: "SubstanceProtein" },
  { key: "substancereferenceinformation", type: "SubstanceReferenceInformation" },
  { key: "substancespecification", type: "SubstanceSpecification" },
  { key: "substancesourcematerial", type: "SubstanceSourceMaterial" },
  { key: "terminologycapabilities", type: "TerminologyCapabilities" },
  { key: "testreport", type: "TestReport" },
  { key: "testscript", type: "TestScript" },
  { key: "valueset", type: "ValueSet" },
];

function camelCase(str) {
  return str.charAt(0).toLowerCase() + str.slice(1);
}

function pluralLabel(type) {
  return type + "s";
}

function writeFile(filePath, content) {
  const dir = path.dirname(filePath);
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(filePath, content, "utf8");
}

const OPS_DIR = path.join(
  __dirname,
  "..",
  "src",
  "data",
  "operations",
  "data"
);

ENTITIES.forEach(({ key, type }) => {
  const c = camelCase(type);
  const createOp = `create${type}Operation`;
  const listOp = `list${pluralLabel(type)}Operation`;
  const getOp = `get${type}ByIdOperation`;
  const updateOp = `update${type}Operation`;
  const deleteOp = `delete${type}Operation`;

  const dir = path.join(OPS_DIR, key);

  writeFile(
    path.join(dir, `${key}-create-operation.ts`),
    `import type { ${type}, Meta } from "@openhi/types";
import { ulid } from "ulid";
import {
  mergeAuditIntoMeta,
  type MetaWithExtensions,
} from "../../../audit-meta";
import { getDynamoDataService } from "../../../dynamo/dynamo-data-service";
import type { OpenHiContext } from "../../../openhi-context";
import {
  createDataEntityRecord,
  type SingleResourceResult,
} from "../../data-operations-common";

/**
 * Create a ${type}. Accepts FHIR-like body and OpenHI context; returns created resource.
 *
 * @see sites/www-docs/content/packages/@openhi/constructs/data/shared-data-layer-layout.md
 */
export interface Create${type}Params {
  context: OpenHiContext;
  /** FHIR ${type} body (resourceType, id optional, meta optional). */
  body: ${type};
  /** Optional table name override; resolved by data service from DYNAMO_TABLE_NAME when omitted. */
  tableName?: string;
}

export type Create${type}Result = SingleResourceResult<${type}>;

/**
 * Creates a ${type}. Builds put attrs with audit in meta, then ElectroDB put.
 * Throws on service/validation errors; adapters map to HTTP/GraphQL.
 */
export async function ${createOp}(
  params: Create${type}Params,
): Promise<Create${type}Result> {
  const { context, body, tableName } = params;
  const { tenantId, workspaceId, date, actorId, actorName } = context;
  const id = body.id ?? ulid();

  const meta: Meta = {
    ...(body.meta ?? {}),
    lastUpdated: date,
    versionId: "1",
  };

  const resourceWithAudit: ${type} & {
    id: string;
    meta: MetaWithExtensions;
  } = {
    ...body,
    resourceType: "${type}",
    id,
    meta: mergeAuditIntoMeta(meta, {
      createdDate: date,
      createdById: actorId,
      createdByName: actorName,
      modifiedDate: date,
      modifiedById: actorId,
      modifiedByName: actorName,
    }),
  };

  const service = getDynamoDataService(tableName);
  return createDataEntityRecord<${type}>(
    service.entities.${key} as Parameters<typeof createDataEntityRecord>[0],
    tenantId,
    workspaceId,
    id,
    resourceWithAudit,
    date,
  );
}
`
  );

  writeFile(
    path.join(dir, `${key}-list-operation.ts`),
    `import type { ${type} } from "@openhi/types";
import { getDynamoDataService } from "../../../dynamo/dynamo-data-service";
import {
  type ListParams,
  listDataEntitiesByWorkspace,
  type ListResult,
  type ListEntry,
} from "../../data-operations-common";

/**
 * List ${type}s in a workspace (GSI1, sharded). Returns domain result for adapters to map to FHIR Bundle or other formats.
 *
 * @see sites/www-docs/content/packages/@openhi/constructs/data/shared-data-layer-layout.md
 */
export type List${pluralLabel(type)}Params = ListParams;

export type ${type}ListEntry = ListEntry<${type}>;

export type List${pluralLabel(type)}Result = ListResult<${type}>;

/**
 * Lists all ${type}s in the workspace. Uses GSI1 (Unified Sharded List per ADR-011).
 * Throws on service errors; adapters map to HTTP/GraphQL/Step Function.
 */
export async function ${listOp}(
  params: List${pluralLabel(type)}Params,
): Promise<List${pluralLabel(type)}Result> {
  const { context, tableName } = params;
  const { tenantId, workspaceId } = context;
  const service = getDynamoDataService(tableName);
  return listDataEntitiesByWorkspace<${type}>(
    service.entities.${key} as Parameters<
      typeof listDataEntitiesByWorkspace
    >[0],
    tenantId,
    workspaceId,
  );
}
`
  );

  writeFile(
    path.join(dir, `${key}-get-by-id-operation.ts`),
    `import type { ${type} } from "@openhi/types";
import { getDynamoDataService } from "../../../dynamo/dynamo-data-service";
import {
  type GetByIdParams,
  getDataEntityById,
  type SingleResourceResult,
} from "../../data-operations-common";

/**
 * Get a single ${type} by id. Throws NotFoundError if not found.
 *
 * @see sites/www-docs/content/packages/@openhi/constructs/data/shared-data-layer-layout.md
 */
export type Get${type}ByIdParams = GetByIdParams;

export type Get${type}ByIdResult = SingleResourceResult<${type}>;

/**
 * Returns the current version of a ${type}. Throws NotFoundError when the resource does not exist.
 */
export async function ${getOp}(
  params: Get${type}ByIdParams,
): Promise<Get${type}ByIdResult> {
  const { context, id, tableName } = params;
  const { tenantId, workspaceId } = context;
  const service = getDynamoDataService(tableName);
  return getDataEntityById<${type}>(
    service.entities.${key} as Parameters<typeof getDataEntityById>[0],
    tenantId,
    workspaceId,
    id,
    "${type}",
  );
}
`
  );

  writeFile(
    path.join(dir, `${key}-update-operation.ts`),
    `import type { ${type} } from "@openhi/types";
import { getDynamoDataService } from "../../../dynamo/dynamo-data-service";
import {
  type BaseDataEntityParams,
  buildUpdatedResourceWithAudit,
  updateDataEntityById,
  type SingleResourceResult,
} from "../../data-operations-common";

/**
 * Update a ${type} by id. Throws NotFoundError if the resource does not exist.
 *
 * @see sites/www-docs/content/packages/@openhi/constructs/data/shared-data-layer-layout.md
 */
export interface Update${type}Params extends BaseDataEntityParams {
  id: string;
  /** FHIR ${type} body (resourceType, id, meta optional). */
  body: ${type};
}

export type Update${type}Result = SingleResourceResult<${type}>;

/**
 * Updates the current version of a ${type}. Merges audit from context into meta.
 * Throws NotFoundError when the resource does not exist.
 */
export async function ${updateOp}(
  params: Update${type}Params,
): Promise<Update${type}Result> {
  const { context, id, body, tableName } = params;
  const { tenantId, workspaceId, date, actorId, actorName } = context;
  const service = getDynamoDataService(tableName);

  return updateDataEntityById<${type}>(
    service.entities.${key} as Parameters<typeof updateDataEntityById>[0],
    tenantId,
    workspaceId,
    id,
    "${type}",
    context,
    (existingResourceStr) =>
      buildUpdatedResourceWithAudit<${type}>(
        body,
        id,
        date,
        actorId,
        actorName,
        existingResourceStr,
        "${type}",
      ),
  );
}
`
  );

  writeFile(
    path.join(dir, `${key}-delete-operation.ts`),
    `import { getDynamoDataService } from "../../../dynamo/dynamo-data-service";
import {
  type BaseDataEntityParams,
  deleteDataEntityById,
} from "../../data-operations-common";

/**
 * Delete a ${type} by id. No-op if the item does not exist (ElectroDB delete is idempotent).
 *
 * @see sites/www-docs/content/packages/@openhi/constructs/data/shared-data-layer-layout.md
 */
export interface Delete${type}Params extends BaseDataEntityParams {
  id: string;
}

/**
 * Deletes the current version of a ${type}. Does not throw when the resource does not exist.
 * Throws on service errors; adapters map to HTTP/GraphQL.
 */
export async function ${deleteOp}(
  params: Delete${type}Params,
): Promise<void> {
  const { context, id, tableName } = params;
  const { tenantId, workspaceId } = context;
  const service = getDynamoDataService(tableName);
  await deleteDataEntityById(
    service.entities.${key} as Parameters<typeof deleteDataEntityById>[0],
    tenantId,
    workspaceId,
    id,
  );
}
`
  );

  writeFile(
    path.join(dir, "index.ts"),
    `/**
 * ${type} data operations. One file per operation; adapters (REST, GraphQL, Step Function) import as needed.
 *
 * @see sites/www-docs/content/packages/@openhi/constructs/data/shared-data-layer-layout.md
 */

export {
  ${createOp},
  type Create${type}Params,
  type Create${type}Result,
} from "./${key}-create-operation";
export {
  ${deleteOp},
  type Delete${type}Params,
} from "./${key}-delete-operation";
export {
  ${getOp},
  type Get${type}ByIdParams,
  type Get${type}ByIdResult,
} from "./${key}-get-by-id-operation";
export {
  ${listOp},
  type List${pluralLabel(type)}Params,
  type List${pluralLabel(type)}Result,
  type ${type}ListEntry,
} from "./${key}-list-operation";
export {
  ${updateOp},
  type Update${type}Params,
  type Update${type}Result,
} from "./${key}-update-operation";
`
  );
});

console.log("Generated operations for", ENTITIES.length, "entities.");
