/*
 * This file is part of the xPack project (http://xpack.github.io).
 * Copyright (c) 2021-2026 Liviu Ionescu. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software
 * for any purpose is hereby granted, under the terms of the MIT license.
 *
 * If a copy of the license was not distributed with this file, it can
 * be obtained from https://opensource.org/license/mit.
 */

// ----------------------------------------------------------------------------

import assert from 'node:assert'
import * as os from 'node:os'

import { Logger } from '@xpack/logger'

// ----------------------------------------------------------------------------

import {
  LiquidSubstitutionsVariables,
  liquidSubstitutionsVariablesBase,
} from '../data/substitutions-variables.js'
import { isJsonObject } from '../functions/is-something.js'
import { Actions } from './actions.js'
import { BuildConfigurations } from './build-configurations.js'
import { LiquidEngine } from './liquid-engine.js'
import { JsonXpmPackage } from '../types/json.js'

// ============================================================================

/**
 * The property name used for the build folder relative path.
 */
export const buildFolderRelativePathPropertyName = 'buildFolderRelativePath'

// ============================================================================

/**
 * Configuration parameters for constructing a data model instance.
 *
 * @remarks
 * This interface defines the required configuration for creating an
 * instance of {@link DataModel}. Both properties are mandatory.
 *
 * The parameters provide the parsed <code>package.json</code> content
 * containing package metadata and <b>xpm</b>-specific configuration, along
 * with the logger for diagnostic output during data model initialization
 * and template processing.
 */
export interface DataModelConstructorParameters {
  /**
   * The JSON package definition.
   */
  jsonPackage: JsonXpmPackage

  /**
   * The logger instance for output and diagnostics.
   */
  log: Logger
}

/**
 * Represents a lazy-loading data model for an <b>xpm</b> package.
 *
 * @remarks
 * This class prepares substitution variables, creates the Liquid
 * engine, and exposes actions and build configurations defined in the package.
 *
 * The package processor serves as the top-level coordinator for all
 * Liquid-based template processing in an <b>xpm</b> package. It establishes the
 * foundation for variable substitution throughout the package hierarchy:
 *
 * <ol>
 * <li>Initializes base substitution variables (platform detection, system
 *    information, etc.).</li>
 * <li>Adds package-specific variables from <code>package.json</code>
 *     metadata.</li>
 * <li>Merges user-defined properties from <code>xpack.properties</code>.</li>
 * <li>Creates package-level actions accessible across all contexts.</li>
 * <li>Creates build configurations, each inheriting the base substitution
 *    context and adding configuration-specific variables.</li>
 * </ol>
 *
 * This hierarchical structure ensures that templates at any level have
 * access to appropriate variables while maintaining clear scoping rules.
 * Package-level actions are available globally, while configuration-level
 * actions are scoped to their respective configurations.
 */
export class DataModel {
  // --------------------------------------------------------------------------
  // Public Members.

  /**
   * The variables available for Liquid substitutions.
   *
   * @remarks
   * This sealed object provides the base substitution context inherited by
   * all actions and build configurations within the package.
   *
   * Variable hierarchy:
   *
   * <ol>
   * <li><b>Base variables (xpmLiquidSubstitutionsVariablesBase):</b>
   *   <ul>
   *   <li><code>env</code>: Environment variables from process.env</li>
   *   <li><code>os</code>: Platform detection (platform, arch, endianness,
   *      version)</li>
   *   <li><code>path</code>: Path utilities (sep, delimiter, cwd)</li>
   *   </ul>
   * </li>
   * <li><b>Package metadata:</b>
   *   <ul>
   *   <li><code>package</code>: Complete <code>package.json</code> content
   *      (name, version,
   *      dependencies, etc.)</li>
   *   </ul>
   * </li>
   * <li><b>User-defined properties:</b>
   *   <ul>
   *   <li><code>properties</code>: Merged from
   *      <code>xpack.properties</code> if present</li>
   *   </ul>
   * </li>
   * </ol>
   *
   * The object is sealed after initialization to prevent accidental
   * modification. Child components (actions and configurations) extend this
   * context with their own scoped variables (configuration, matrix) without
   * modifying the original sealed object.
   */
  readonly substitutionsVariables: LiquidSubstitutionsVariables

  /**
   * The actions collection for this package.
   *
   * @remarks
   * This collection manages package-level actions defined in
   * `xpack.actions`, which are globally accessible and not tied to specific
   * build configurations.
   *
   * Package-level actions characteristics:
   *
   * <ol>
   * <li>Created during construction but initially unpopulated.</li>
   * <li>Populated during the collection's own initialisation when
   *    <code>Actions.initialise()</code> is called.</li>
   * <li>Have access to package-level substitution variables but not
   *    configuration-specific variables.</li>
   * <li>Suitable for package-wide tasks like testing, documentation
   *    generation, or global cleanup.</li>
   * <li>Can be used alongside configuration-specific actions, which inherit
   *    from package-level actions.</li>
   * </ol>
   */
  readonly actions: Actions

  /**
   * The build configurations collection for this package.
   *
   * @remarks
   * This collection manages all build configurations defined in
   * `xpack.buildConfigurations`, supporting inheritance, template expansion,
   * and configuration-specific properties and dependencies.
   *
   * Build configurations characteristics:
   *
   * <ol>
   * <li>Created during construction but initially unpopulated.</li>
   * <li>Populated during the collection's own initialisation when
   *    <code>BuildConfigurations.initialise()</code> is called.</li>
   * <li>Each configuration inherits the package-level substitution variables
   *    and extends them with configuration-specific context.</li>
   * <li>Support complex inheritance chains where configurations can inherit
   *    properties, dependencies, and actions from other configurations.</li>
   * <li>Can be generated from templates with matrix expansion for
   *    multi-platform or multi-variant builds.</li>
   * <li>Each configuration maintains its own actions collection, inheriting
   *    package-level actions and adding configuration-specific ones.</li>
   * </ol>
   */
  readonly buildConfigurations: BuildConfigurations

  /**
   * The logger instance for output and diagnostics.
   *
   * @remarks
   * This logger provides trace-level diagnostics for the entire package
   * processing hierarchy, including Liquid engine creation, variable
   * initialization, action collection setup, and build configuration
   * preparation. It's passed down to child components (actions and build
   * configurations) to maintain consistent logging throughout the package
   * lifecycle.
   */
  protected readonly _log: Logger

  // --------------------------------------------------------------------------
  // Private Members.

  /**
   * The Liquid engine used for substitutions.
   *
   * @remarks
   * This LiquidEngine instance is configured with strict mode and custom
   * filters for xpm-specific operations. It's shared across all actions and
   * build configurations within the package, ensuring consistent template
   * processing behavior.
   *
   * Engine characteristics:
   *
   * <ol>
   * <li>Strict mode enabled to catch undefined variable references.</li>
   * <li>Custom filters for platform detection (<code>isPlatform</code>,
   *    <code>isArch</code>).</li>
   * <li>Custom filters for path sanitization (<code>filterPath</code>,
   *    <code>filterPosixPath</code>,
   *    <code>filterWin32Path</code>).</li>
   * <li>Shared instance reduces memory overhead and ensures consistent
   *    template evaluation across all package components.</li>
   * </ol>
   */
  protected readonly _engine: LiquidEngine

  /**
   * The JSON package definition.
   *
   * @remarks
   * This object contains the complete `package.json` content, including both
   * standard npm fields and xpm-specific extensions in the `xpack` section.
   *
   * Required structure:
   *
   * <ol>
   * <li>Standard npm fields: name, version, dependencies, devDependencies.</li>
   * <li>Required <code>xpack</code> section containing xpm-specific
   *    configuration.</li>
   * <li>Optional <code>xpack.properties</code> for user-defined
   *    substitution variables.</li>
   * <li>Optional <code>xpack.actions</code> for package-level executable
   *    actions.</li>
   * <li>Optional <code>xpack.buildConfigurations</code> for build configuration
   *    definitions.</li>
   * </ol>
   *
   * The package definition is validated during construction, requiring the
   * `xpack` section to be present and be a valid JSON object.
   */
  protected readonly _jsonPackage: JsonXpmPackage

  // --------------------------------------------------------------------------
  // Constructor.

  /**
   * Constructs a Liquid package processor.
   *
   * @remarks
   * The constructor initializes the Liquid engine and prepares the
   * substitution variables context that will be inherited by all actions
   * and build configurations.
   *
   * Initialization sequence:
   *
   * <ol>
   * <li>Create <code>LiquidEngine</code> with custom filters and strict
   * configuration.</li>
   * <li>Validate <code>xpack</code> section exists in
   *    <code>package.json</code>.</li>
   * <li>Initialize base substitution variables (os, platform, arch, etc.).</li>
   * <li>Add package metadata to substitution context.</li>
   * <li>Merge <code>xpack.properties</code> if defined, allowing user-defined
   * variables.</li>
   * <li>Seal substitution variables to prevent accidental modification.</li>
   * <li>Create package-level actions collection (initially empty, populated
   *    during initialisation).</li>
   * <li>Create build configurations collection (initially empty, populated
   *    during initialisation).</li>
   * </ol>
   *
   * The substitution variables object is sealed to ensure immutability of
   * the base context. Individual actions and configurations will extend this
   * context with their own scoped variables without modifying the original.
   *
   * @param jsonPackage - The JSON package definition.
   * @param log - The logger instance for output and diagnostics.
   */
  constructor({ jsonPackage, log }: DataModelConstructorParameters) {
    log.trace(`${DataModel.name}()`)

    this._log = log
    this._engine = new LiquidEngine()

    assert(
      isJsonObject(jsonPackage.xpack),
      'xpack section missing in package.json'
    )
    this._jsonPackage = jsonPackage

    // os.version() available since 12.x
    assert(
      typeof os.version === 'function',
      'Mandatory os.version available only since 12.x'
    )

    this.substitutionsVariables = {
      ...liquidSubstitutionsVariablesBase,
      package: jsonPackage,
    }

    if (isJsonObject(jsonPackage.xpack.properties)) {
      this.substitutionsVariables.properties = {
        ...jsonPackage.xpack.properties,
      }
    }

    // Prevent adding/removing properties.
    Object.seal(this.substitutionsVariables)

    // Empty actions.
    this.actions = new Actions({
      log: this._log,
      engine: this._engine,
      substitutionsVariables: this.substitutionsVariables,
      jsonActions: this._jsonPackage.xpack.actions,
    })

    // Empty build configurations.
    this.buildConfigurations = new BuildConfigurations({
      log: this._log,
      engine: this._engine,
      substitutionsVariables: this.substitutionsVariables,
      jsonBuildConfigurations: this._jsonPackage.xpack.buildConfigurations,
    })
  }
}

// ----------------------------------------------------------------------------
