// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

// What the file said about itself, kept so that writing it back says the same.
//
// A PNG can carry the gamma it was authored for, the primaries of the display
// it was checked on, an ICC profile, and any number of text notes. None of that
// changes a pixel, and none of it is interpreted here: colour management is not
// this library's business. It is read, carried, and written out again, because a
// decoder that drops it turns an image that was correct on one screen into one
// that is wrong on all of them.
//
// The same record travels with every format that has somewhere to put it. Where
// a format has no room, the field is simply not written.

///|
/// What a column of pixels was meant to be seen in.
pub(all) enum Intent {
  /// For a picture.
  Perceptual
  /// For a colour that should come out as it went in.
  Relative
  /// For a diagram, where saturation matters more than hue.
  Saturation
  /// For comparing against a proof.
  Absolute
} derive(Eq, Debug)

///|
pub extend Intent with Eq::{equal, not_equal}

///|
pub extend Intent with Debug::{to_repr}

///|
/// The number a file writes for this intent, which is how PNG's `sRGB` chunk
/// numbers them.
pub fn Intent::code(self : Intent) -> Int32 {
  match self {
    Perceptual => 0
    Relative => 1
    Saturation => 2
    Absolute => 3
  }
}

///|
/// The intent a number stands for, or `None` if it is not one of the four.
pub fn Intent::of(code : Int32) -> Intent? {
  match code {
    0 => Some(Perceptual)
    1 => Some(Relative)
    2 => Some(Saturation)
    3 => Some(Absolute)
    _ => None
  }
}

///|
/// The white point and the three primaries, as chromaticity coordinates.
///
/// Eight numbers rather than a matrix: this is what a file writes, and a matrix
/// would be a conversion, which this library does not do.
pub(all) struct Chromaticities {
  white_x : Double
  white_y : Double
  red_x : Double
  red_y : Double
  green_x : Double
  green_y : Double
  blue_x : Double
  blue_y : Double
} derive(Eq, Debug)

///|
pub extend Chromaticities with Eq::{equal, not_equal}

///|
pub extend Chromaticities with Debug::{to_repr}

///|
/// The primaries of sRGB, which is what almost every file means when it says
/// nothing.
pub fn Chromaticities::srgb() -> Chromaticities {
  {
    white_x: 0.3127,
    white_y: 0.3290,
    red_x: 0.6400,
    red_y: 0.3300,
    green_x: 0.3000,
    green_y: 0.6000,
    blue_x: 0.1500,
    blue_y: 0.0600,
  }
}

///|
/// An embedded ICC profile, kept by name and bytes. Not applied: a caller that
/// wants colour management hands these bytes to something that does it.
pub(all) struct Icc {
  name : String
  profile : Bytes
} derive(Eq, Debug)

///|
pub extend Icc with Eq::{equal, not_equal}

///|
pub extend Icc with Debug::{to_repr}

///|
/// A text note, in the flavour of the chunk it came from, so that writing the
/// image back produces the same kind of chunk.
pub(all) enum Text {
  /// `tEXt`: Latin-1, one keyword and one line.
  Latin(keyword~ : String, text~ : String)
  /// `zTXt`: the same, deflated.
  Compressed(keyword~ : String, text~ : String)
  /// `iTXt`: UTF-8, with a language tag and a translated keyword.
  International(
    keyword~ : String,
    language~ : String,
    translated~ : String,
    text~ : String
  )
} derive(Eq, Debug)

///|
pub extend Text with Eq::{equal, not_equal}

///|
pub extend Text with Debug::{to_repr}

///|
/// The keyword a note is filed under.
pub fn Text::keyword(self : Text) -> String {
  match self {
    Latin(keyword~, ..)
    | Compressed(keyword~, ..)
    | International(keyword~, ..) => keyword
  }
}

///|
/// What the note says.
pub fn Text::text(self : Text) -> String {
  match self {
    Latin(text~, ..) | Compressed(text~, ..) | International(text~, ..) => text
  }
}

///|
/// Everything the file said about itself besides its pixels.
pub(all) struct Metadata {
  /// The file gamma: the exponent the samples were encoded with, so that 0.45455
  /// means what sRGB means.
  gamma : Double?
  /// The primaries the image was authored for.
  chromaticities : Chromaticities?
  /// The rendering intent, which only means anything next to an sRGB or ICC tag.
  intent : Intent?
  /// The embedded profile, if there was one.
  icc : Icc?
  /// The text notes, in the order the file had them.
  texts : Array[Text]
} derive(Eq, Debug)

///|
pub extend Metadata with Eq::{equal, not_equal}

///|
pub extend Metadata with Debug::{to_repr}

///|
/// No metadata at all, which is what an image built in memory has.
pub fn Metadata::default() -> Metadata {
  { gamma: None, chromaticities: None, intent: None, icc: None, texts: [], }
}

///|
/// Whether there is nothing to write.
pub fn Metadata::is_empty(self : Metadata) -> Bool {
  self.gamma is None &&
  self.chromaticities is None &&
  self.intent is None &&
  self.icc is None &&
  self.texts.is_empty()
}