import { geliosProviderPackageV6 } from "../v6/package.mjs";

export const GELIOS_PROVIDER_PACKAGE_ID = "gelios.provider.v7";
export const GELIOS_PROVIDER_PACKAGE_VERSION = "7.0.0";
export const GELIOS_POSITIONS_DATA_PRODUCT_ID = "fleet.positions.current.v5";
export const GELIOS_POSITIONS_DATA_PRODUCT_VERSION = "5.0.0";
export const GELIOS_POSITIONS_ONTOLOGY_REVISION = "ontology.map.moving_object.v3";
export const GELIOS_UNIT_SOURCE_ID_PREFIX = "gelios-unit-";

const PREVIOUS_PRODUCT_ID = "fleet.positions.current.v4";
const PREVIOUS_FIELD_POLICY_ID = "gelios.positions.current.fields.v4";
const PREVIOUS_REALTIME_PROFILE_ID = "gelios.positions.current.realtime.v5";
const PREVIOUS_MANUAL_PROFILE_ID = "gelios.positions.current.manual.v5";
const PREVIOUS_MAPPING_ID = "gelios.units.to.fleet.positions.current.v5";
const PREVIOUS_TEMPLATE_ID = "gelios.positions.current.l2.v5";
const FIELD_POLICY_ID = "gelios.positions.current.fields.v5";
const REALTIME_PROFILE_ID = "gelios.positions.current.realtime.v7";
const MANUAL_PROFILE_ID = "gelios.positions.current.manual.v7";
const MAPPING_ID = "gelios.units.to.fleet.positions.current.v7";
const TEMPLATE_ID = "gelios.positions.current.l2.v7";

const positionFields = Object.freeze([
  "course_degrees",
  "display_name",
  "elevation_meters",
  "engine_hours",
  "geometry",
  "hdop",
  "horizontal_accuracy_meters",
  "mileage_km",
  "movement_state",
  "object_kind",
  "position_source",
  "satellite_count",
  "sensor_readings",
  "signal_state",
  "speed_kph",
]);

const value = structuredClone(geliosProviderPackageV6);
const previousMapping = value.mappingContracts.find((mapping) => mapping.id === PREVIOUS_MAPPING_ID);
if (!previousMapping) throw new Error("gelios_v7_previous_mapping_missing");

value.id = GELIOS_PROVIDER_PACKAGE_ID;
value.version = GELIOS_PROVIDER_PACKAGE_VERSION;
value.manifest = {
  ...value.manifest,
  id: "gelios.provider.manifest.v7",
  version: GELIOS_PROVIDER_PACKAGE_VERSION,
  fieldPolicyIds: replaceOne(value.manifest.fieldPolicyIds, PREVIOUS_FIELD_POLICY_ID, FIELD_POLICY_ID),
  collectionProfileIds: replaceOne(
    replaceOne(value.manifest.collectionProfileIds, PREVIOUS_REALTIME_PROFILE_ID, REALTIME_PROFILE_ID),
    PREVIOUS_MANUAL_PROFILE_ID,
    MANUAL_PROFILE_ID,
  ),
  dataProductIds: replaceOne(value.manifest.dataProductIds, PREVIOUS_PRODUCT_ID, GELIOS_POSITIONS_DATA_PRODUCT_ID),
  mappingContractIds: replaceOne(value.manifest.mappingContractIds, PREVIOUS_MAPPING_ID, MAPPING_ID),
  l2TemplateIds: replaceOne(value.manifest.l2TemplateIds, PREVIOUS_TEMPLATE_ID, TEMPLATE_ID),
};

value.capabilities = value.capabilities.map((capability) => (
  capability.id === "gelios.units.current.read"
    ? {
      ...capability,
      request: {
        ...capability.request,
        query: {
          ...capability.request.query,
          incltrip: "true",
          inclcntrs: "true",
          inclsnsrs: "true",
          incllsv: "true",
        },
      },
    }
    : capability
));

value.fieldPolicies = value.fieldPolicies.map((policy) => (
  policy.id === PREVIOUS_FIELD_POLICY_ID
    ? {
      ...policy,
      id: FIELD_POLICY_ID,
      version: GELIOS_POSITIONS_DATA_PRODUCT_VERSION,
      dataProductId: GELIOS_POSITIONS_DATA_PRODUCT_ID,
      targetFields: [...positionFields],
      restrictedSourcePaths: policy.restrictedSourcePaths
        .filter((path) => path !== "sensors")
        .concat(["sensors.conversionTable"]),
    }
    : policy
));

value.collectionProfiles = value.collectionProfiles.map((profile) => {
  if (profile.dataProductId !== PREVIOUS_PRODUCT_ID) return profile;
  return {
    ...profile,
    id: profile.mode === "realtime" ? REALTIME_PROFILE_ID : MANUAL_PROFILE_ID,
    version: GELIOS_PROVIDER_PACKAGE_VERSION,
    dataProductId: GELIOS_POSITIONS_DATA_PRODUCT_ID,
    mappingContractId: MAPPING_ID,
    fieldPolicyId: FIELD_POLICY_ID,
    l2TemplateId: TEMPLATE_ID,
  };
});

value.dataProducts = value.dataProducts.map((product) => (
  product.id === PREVIOUS_PRODUCT_ID
    ? {
      id: GELIOS_POSITIONS_DATA_PRODUCT_ID,
      version: GELIOS_POSITIONS_DATA_PRODUCT_VERSION,
      ontologyRevision: GELIOS_POSITIONS_ONTOLOGY_REVISION,
      deliveryMode: "snapshot+patch",
      semanticTypes: ["map.moving_object"],
      fields: [...positionFields],
      fieldContracts: {
        course_degrees: { type: "number", required: false, minimum: 0, maximum: 360 },
        display_name: { type: "string", required: true },
        elevation_meters: { type: "number", required: false },
        engine_hours: { type: "number", required: false, minimum: 0 },
        geometry: { type: "point", required: false },
        hdop: { type: "number", required: false, minimum: 0 },
        horizontal_accuracy_meters: { type: "number", required: false, minimum: 0 },
        mileage_km: { type: "number", required: false, minimum: 0 },
        movement_state: { type: "string", required: true, enum: ["moving", "stopped"] },
        object_kind: { type: "string", required: true },
        position_source: { type: "string", required: true },
        satellite_count: { type: "number", required: false, minimum: 0 },
        sensor_readings: { type: "telemetry_readings", required: false },
        signal_state: { type: "string", required: true, enum: ["active", "inactive"] },
        speed_kph: { type: "number", required: false, minimum: 0 },
      },
      history: { mode: "sampled", intervalMs: 60_000, strategy: "latest-per-entity-per-bucket", retentionDays: 90 },
    }
    : product
));

value.mappingContracts = value.mappingContracts.map((mapping) => {
  if (mapping.id !== PREVIOUS_MAPPING_ID) return mapping;
  return {
    ...mapping,
    id: MAPPING_ID,
    version: GELIOS_PROVIDER_PACKAGE_VERSION,
    fieldPolicyId: FIELD_POLICY_ID,
    target: {
      ...mapping.target,
      dataProductId: GELIOS_POSITIONS_DATA_PRODUCT_ID,
      version: GELIOS_POSITIONS_DATA_PRODUCT_VERSION,
      ontologyRevision: GELIOS_POSITIONS_ONTOLOGY_REVISION,
    },
    derivations: {
      ...mapping.derivations,
      sensor_readings: {
        kind: "bounded_readings",
        rules: ["last_sensor_values.normalized"],
        default: [],
        parameters: { maxReadings: 128 },
      },
    },
    fact: {
      ...mapping.fact,
      attributes: {
        ...mapping.fact.attributes,
        engine_hours: {
          strategy: "first_non_empty",
          paths: ["counters.engineHours.value"],
          coerce: "number",
          minimum: 0,
          omitIfMissing: true,
          omitIfInvalid: true,
        },
        mileage_km: {
          strategy: "first_non_empty",
          paths: ["counters.mileage.value"],
          coerce: "number",
          minimum: 0,
          omitIfMissing: true,
          omitIfInvalid: true,
        },
        sensor_readings: { derive: "sensor_readings" },
      },
    },
  };
});

value.l2Templates = value.l2Templates.map((template) => {
  if (template.id !== PREVIOUS_TEMPLATE_ID) return template;
  return {
    ...template,
    id: TEMPLATE_ID,
    version: GELIOS_PROVIDER_PACKAGE_VERSION,
    steps: template.steps.map((step) => {
      if (step.kind === "semantic_mapping") return { ...step, mappingContractId: MAPPING_ID };
      if (step.kind === "data_product_publish") return { ...step, dataProductId: GELIOS_POSITIONS_DATA_PRODUCT_ID };
      return step;
    }),
  };
});

export const geliosProviderPackageV7 = deepFreeze(value);

function replaceOne(items, previous, next) {
  if (!Array.isArray(items) || items.filter((item) => item === previous).length !== 1) {
    throw new Error(`gelios_v7_manifest_predecessor_invalid:${previous}`);
  }
  return items.map((item) => item === previous ? next : item);
}

function deepFreeze(input) {
  if (!input || typeof input !== "object" || Object.isFrozen(input)) return input;
  Object.freeze(input);
  for (const child of Object.values(input)) deepFreeze(child);
  return input;
}
