// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
pub struct Composer {
  priv module_sets : Map[String, ComposableModuleDefinition]
  priv registry : WgslSourceRegistry
  priv composable_module_registry_paths : Map[String, String]
  priv processed_module_cache : Map[String, WgslProcessedComposeModule]
}

///|
pub fn Composer::default() -> Composer {
  {
    module_sets: Map([]),
    registry: WgslSourceRegistry::default(),
    composable_module_registry_paths: Map([]),
    processed_module_cache: Map([]),
  }
}

///|
pub(all) suberror ComposerError {
  FailedToResolveImport(String)
  ImportCycle(String)
  InvalidStructMemberIdentifier(String)
  InvalidWgslSyntax(String, Int32)
  InvalidOverride(String)
  MissingRegisteredSource(String)
  MissingPreparedSourceDefs(String)
  MissingStartupRegisteredSource(String)
  NoModuleName(String)
  DefineInModule
  GlslInvalidVersion
  InvalidWgslDirective(String, String)
  InvalidIr(String)
  ImmediateSpecializationFailed(String)
  AmbiguousImportBinding(String)
  UnknownShaderDef(String)
  UnknownShaderDefOperator(String)
  InvalidShaderDefComparisonValue(String, String, String)
  ElseWithoutCondition
  TooManyEndIfs
  NotEnoughEndIfs
}

///|
pub fn ComposerError::message(self : ComposerError) -> String {
  match self {
    FailedToResolveImport(import_path) =>
      match wgsl_required_import_name_from_error(import_path) {
        Some(name) => "error: required import '\{name}' not found"
        None => "failed to resolve shader import: \{import_path}"
      }
    ImportCycle(cycle) => "shader import cycle detected: \{cycle}"
    InvalidStructMemberIdentifier(name) =>
      "composable module identifier requires AST identifier normalization: \{name}"
    InvalidWgslSyntax(message, offset) =>
      "invalid WGSL syntax at \{offset}: \{message}"
    InvalidOverride(name) =>
      "error: override is invalid as `\{name}` is not virtual (this error can be disabled with feature 'override_any')"
    MissingRegisteredSource(rel) => "missing registered WGSL source: \{rel}"
    MissingPreparedSourceDefs(rel) =>
      "missing prepared shader defs for WGSL source: \{rel}"
    MissingStartupRegisteredSource(rel) =>
      "shader source was not startup-registered: \{rel}"
    NoModuleName(path) =>
      "shader module is missing #define_import_path: \{path}"
    DefineInModule => "shader module cannot contain #define directives"
    GlslInvalidVersion => "invalid GLSL #version directive"
    InvalidWgslDirective(line, reason) =>
      "invalid WGSL directive: \{line}: \{reason}"
    InvalidIr(message) => "invalid WGSL IR pipeline: \{message}"
    ImmediateSpecializationFailed(message) =>
      "invalid immediate specialization: \{message}"
    AmbiguousImportBinding(message) =>
      "ambiguous shader import binding: \{message}"
    UnknownShaderDef(name) => "unknown shader def: \{name}"
    UnknownShaderDefOperator(operator) =>
      "unknown shader def operator: \{operator}"
    InvalidShaderDefComparisonValue(name, value, expected) =>
      "invalid shader def comparison value for \{name}: \{value}; expected \{expected}"
    ElseWithoutCondition => "#else without condition"
    TooManyEndIfs => "too many #endif directives"
    NotEnoughEndIfs => "not enough #endif directives"
  }
}

///|
fn wgsl_required_import_name_from_error(import_path : String) -> String? {
  let (path, from_reference) = match
    import_path.split(" in function ").collect() {
    [head, ..] => (head.to_owned().trim().to_owned(), true)
    _ => (import_path.trim().to_owned(), false)
  }
  if path == "" {
    return None
  }
  let name = if from_reference {
    match path.split("::").collect() {
      [head, ..] => head.to_owned()
      _ => path
    }
  } else {
    path
  }
  if name == "" {
    return None
  }
  for index in 0..= 97 && code <= 122) ||
      (code >= 65 && code <= 90) ||
      (code >= 48 && code <= 57) ||
      code == 95
    if !valid {
      return None
    }
  }
  Some(name)
}