// Copyright 2025 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.

///|
/// Represents an attribute in a Pug element
pub struct Attribute {
  name : String
  value : String? // None for boolean attributes, Some("") for empty values
  unescaped : Bool // True for != syntax, false for = syntax
} derive(Show, Eq, ToJson)

///|
/// Locals map for template interpolation
pub struct Locals(Map[String, String])

///|
pub fn Locals::new() -> Locals {
  Locals({})
}

///|
pub fn Locals::get(self : Locals, key : String) -> String? {
  self.0.get(key)
}

///|
pub fn Locals::set(self : Locals, key : String, value : String) -> Unit {
  self.0[key] = value
}

///|
/// Set an array value (stub for future implementation)
pub fn Locals::set_array(
  self : Locals,
  key : String,
  values : Array[String],
) -> Unit {
  // Store as JSON-like array representation for now
  let buf = StringBuilder::new()
  buf.write_string("[")
  for i, v in values {
    if i > 0 {
      buf.write_string(",")
    }
    buf.write_string("\"")
    buf.write_string(v)
    buf.write_string("\"")
  }
  buf.write_string("]")
  self.0[key] = buf.to_string()
}

///|
/// Set an object value (stub for future implementation)
pub fn Locals::set_object(
  self : Locals,
  key : String,
  obj : Map[String, String],
) -> Unit {
  // Store as JSON-like object representation for now
  let buf = StringBuilder::new()
  buf.write_string("{")
  let mut first = true
  for k, v in obj {
    if not(first) {
      buf.write_string(",")
    }
    first = false
    buf.write_string("\"")
    buf.write_string(k)
    buf.write_string("\":\"")
    buf.write_string(v)
    buf.write_string("\"")
  }
  buf.write_string("}")
  self.0[key] = buf.to_string()
}

///|
/// Set a nested array value (stub for future implementation)
pub fn Locals::set_nested_array(
  self : Locals,
  key : String,
  rows : Array[Array[String]],
) -> Unit {
  // Store as JSON-like nested array representation for now
  let buf = StringBuilder::new()
  buf.write_string("[")
  for i, row in rows {
    if i > 0 {
      buf.write_string(",")
    }
    buf.write_string("[")
    for j, cell in row {
      if j > 0 {
        buf.write_string(",")
      }
      buf.write_string("\"")
      buf.write_string(cell)
      buf.write_string("\"")
    }
    buf.write_string("]")
  }
  buf.write_string("]")
  self.0[key] = buf.to_string()
}

///|
/// Represents a node in the Pug AST
pub enum Node {
  /// An HTML element with tag, id, classes, attributes, and children
  Element(
    tag~ : String,
    id~ : String,
    classes~ : Array[String],
    attributes~ : Array[Attribute],
    children~ : Array[Node],
    self_closing~ : Bool
  )
  /// Plain text content
  Text(String)
  /// Interpolated variable #{name} (escaped)
  Interpolation(String)
  /// Unescaped interpolated variable !{name}
  UnescapedInterpolation(String)
  /// A comment (can be rendered or not)
  Comment(text~ : String, render~ : Bool)
  /// Document type declaration
  Doctype(String)
  /// Conditional (if/unless with optional else)
  Conditional(
    condition~ : String,
    if_true~ : Array[Node],
    if_false~ : Array[Node],
    is_unless~ : Bool
  )
  /// Each loop (each item in collection)
  Each(
    item_var~ : String,
    index_var~ : String, // Empty string if no index variable
    collection~ : String,
    body~ : Array[Node],
    else_body~ : Array[Node]
  ) // For "each ... else" when collection is empty
  /// Case/switch statement
  Case(
    expr~ : String, // Variable to match against
    cases~ : Array[(String, Array[Node])], // (value, body) pairs for when clauses
    default~ : Array[Node]
  ) // Default case body
  /// When clause (used during parsing, not in final AST)
  When(value~ : String, body~ : Array[Node])
  /// Default clause (used during parsing, not in final AST)
  Default(body~ : Array[Node])
  /// Mixin definition (params are (name, default_value) pairs, rest params prefixed with "...")
  MixinDef(
    name~ : String,
    params~ : Array[(String, String)],
    body~ : Array[Node]
  )
  /// Mixin call (block is content passed to the mixin, attrs are extra attributes)
  MixinCall(
    name~ : String,
    args~ : Array[String],
    block~ : Array[Node],
    attrs~ : Array[(String, String)]
  )
  /// Block placeholder (renders mixin block content)
  Block
  /// Variable assignment (- var name = value)
  VarAssign(name~ : String, value~ : String)
  /// While loop
  While(condition~ : String, body~ : Array[Node])
  /// Named block (for template inheritance)
  /// mode: "replace" (default), "append", or "prepend"
  NamedBlock(name~ : String, body~ : Array[Node], mode~ : String)
  /// Include statement (for template inclusion)
  Include(path~ : String)
  /// Include with filter (for filtered template inclusion)
  IncludeFiltered(filter~ : String, path~ : String)
  /// Extends statement (for template inheritance)
  Extends(path~ : String)
  /// Filter statement (:filter_name content)
  Filter(name~ : String, content~ : String)
} derive(Show, Eq, ToJson)

///|
/// A Pug document is a list of nodes
pub struct Document {
  nodes : Array[Node]
} derive(ToJson)

///|
pub fn Document::new() -> Document {
  { nodes: [] }
}

///|
pub fn Document::push(self : Document, node : Node) -> Unit {
  self.nodes.push(node)
}

///|
pub fn Document::iter(self : Document) -> Iter[Node] {
  self.nodes.iter()
}

///|
/// Template registry for storing and resolving templates
/// Used for extends and include functionality
pub struct TemplateRegistry {
  templates : Map[String, String] // path -> source code
}

///|
pub fn TemplateRegistry::new() -> TemplateRegistry {
  { templates: {} }
}

///|
/// Register a template with a path
pub fn TemplateRegistry::register(
  self : TemplateRegistry,
  path : String,
  source : String,
) -> Unit {
  self.templates[path] = source
}

///|
/// Get a template by path
pub fn TemplateRegistry::get(self : TemplateRegistry, path : String) -> String? {
  self.templates.get(path)
}

///|
/// Check if a template exists
pub fn TemplateRegistry::contains(
  self : TemplateRegistry,
  path : String,
) -> Bool {
  self.templates.contains(path)
}

///|
/// Load a template from file and register it
pub fn TemplateRegistry::load_file(
  self : TemplateRegistry,
  path : String,
) -> Unit raise @fs.IOError {
  let source = @fs.read_file_to_string(path)
  self.templates[path] = source
}

///|
/// Load a template from file if not already loaded
pub fn TemplateRegistry::ensure_loaded(
  self : TemplateRegistry,
  path : String,
) -> Unit raise @fs.IOError {
  if not(self.templates.contains(path)) {
    self.load_file(path)
  }
}