using UnityEngine;

namespace eagle {
    /// <summary>
    /// Generates a distance-to-border texture from the province ID map at startup.
    /// Each pixel stores the Euclidean distance (Chamfer approximation, in texels) to
    /// the nearest province boundary, clamped to 255. The shader uses this to render
    /// variable-width borders with a single texture sample per fragment.
    ///
    /// Also generates a cross-province ID map: for each pixel, the province ID on the
    /// other side of the nearest border. The shader uses this to suppress highlight
    /// borders between provinces in the same highlight group.
    /// </summary>
    public class ProvinceBorderDistanceGenerator : MonoBehaviour {
        public ProvinceIDLoader provinceIDLoader;
        public Material provinceMapMaterial;

        private Texture2D _distanceTexture;
        private Texture2D _crossProvinceTexture;

        void Awake() { provinceIDLoader.OnMapLoaded += GenerateDistanceTextures; }

        private void GenerateDistanceTextures() {
            var mapBytes = provinceIDLoader.DecompressedBytes;
            int w = provinceIDLoader.mapWidth;
            int h = provinceIDLoader.mapHeight;

            var (dist, crossIds) = ComputeDistanceMap(mapBytes, w, h);

            _distanceTexture = new Texture2D(w, h, TextureFormat.R8, false);
            _distanceTexture.filterMode = FilterMode.Bilinear;
            _distanceTexture.wrapMode = TextureWrapMode.Clamp;
            _distanceTexture.LoadRawTextureData(dist);
            _distanceTexture.Apply();

            _crossProvinceTexture = new Texture2D(w, h, TextureFormat.R8, false);
            _crossProvinceTexture.filterMode = FilterMode.Point;
            _crossProvinceTexture.wrapMode = TextureWrapMode.Clamp;
            _crossProvinceTexture.LoadRawTextureData(crossIds);
            _crossProvinceTexture.Apply();

            provinceMapMaterial.SetTexture("_BorderDistanceMap", _distanceTexture);
            provinceMapMaterial.SetTexture("_CrossProvinceIDMap", _crossProvinceTexture);
        }

        private static (byte[] dist, byte[] crossIds) ComputeDistanceMap(byte[] ids, int w, int h) {
            const float DIAG = 1.414f;
            int size = w * h;
            var dist = new float[size];
            var crossIds = new byte[size];

            // Init: border pixels = 0, interior = large value
            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    int idx = y * w + x;
                    byte id = ids[idx];

                    // Ocean/invalid pixels are always border
                    if (id == 0 || id == 0xFF) {
                        dist[idx] = 0f;
                        crossIds[idx] = id;
                        continue;
                    }

                    // Check 8 neighbors for different province ID
                    bool isBorder = false;
                    byte crossId = id;
                    for (int dy = -1; dy <= 1 && !isBorder; dy++) {
                        for (int dx = -1; dx <= 1 && !isBorder; dx++) {
                            if (dx == 0 && dy == 0) continue;
                            int nx = x + dx, ny = y + dy;
                            if (nx < 0 || nx >= w || ny < 0 || ny >= h) {
                                isBorder = true;
                                crossId = 0;
                            } else if (ids[ny * w + nx] != id) {
                                isBorder = true;
                                crossId = ids[ny * w + nx];
                            }
                        }
                    }

                    dist[idx] = isBorder ? 0f : 255f;
                    crossIds[idx] = crossId;
                }
            }

            // Forward pass (top-left to bottom-right)
            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    int idx = y * w + x;
                    if (dist[idx] == 0f) continue;

                    float d = dist[idx];
                    int bestIdx = idx;

                    if (x > 0 && dist[idx - 1] + 1f < d) {
                        d = dist[idx - 1] + 1f;
                        bestIdx = idx - 1;
                    }
                    if (y > 0 && dist[(y - 1) * w + x] + 1f < d) {
                        d = dist[(y - 1) * w + x] + 1f;
                        bestIdx = (y - 1) * w + x;
                    }
                    if (x > 0 && y > 0 && dist[(y - 1) * w + x - 1] + DIAG < d) {
                        d = dist[(y - 1) * w + x - 1] + DIAG;
                        bestIdx = (y - 1) * w + x - 1;
                    }
                    if (x < w - 1 && y > 0 && dist[(y - 1) * w + x + 1] + DIAG < d) {
                        d = dist[(y - 1) * w + x + 1] + DIAG;
                        bestIdx = (y - 1) * w + x + 1;
                    }

                    dist[idx] = d;
                    if (bestIdx != idx) { crossIds[idx] = crossIds[bestIdx]; }
                }
            }

            // Backward pass (bottom-right to top-left)
            for (int y = h - 1; y >= 0; y--) {
                for (int x = w - 1; x >= 0; x--) {
                    int idx = y * w + x;
                    if (dist[idx] == 0f) continue;

                    float d = dist[idx];
                    int bestIdx = idx;

                    if (x < w - 1 && dist[idx + 1] + 1f < d) {
                        d = dist[idx + 1] + 1f;
                        bestIdx = idx + 1;
                    }
                    if (y < h - 1 && dist[(y + 1) * w + x] + 1f < d) {
                        d = dist[(y + 1) * w + x] + 1f;
                        bestIdx = (y + 1) * w + x;
                    }
                    if (x < w - 1 && y < h - 1 && dist[(y + 1) * w + x + 1] + DIAG < d) {
                        d = dist[(y + 1) * w + x + 1] + DIAG;
                        bestIdx = (y + 1) * w + x + 1;
                    }
                    if (x > 0 && y < h - 1 && dist[(y + 1) * w + x - 1] + DIAG < d) {
                        d = dist[(y + 1) * w + x - 1] + DIAG;
                        bestIdx = (y + 1) * w + x - 1;
                    }

                    dist[idx] = d;
                    if (bestIdx != idx) { crossIds[idx] = crossIds[bestIdx]; }
                }
            }

            // Pack into byte arrays
            var distResult = new byte[size];
            for (int i = 0; i < size; i++) {
                distResult[i] = (byte)Mathf.Min(Mathf.RoundToInt(dist[i]), 255);
            }
            return (distResult, crossIds);
        }

        void OnDestroy() {
            if (_distanceTexture != null) { Destroy(_distanceTexture); }
            if (_crossProvinceTexture != null) { Destroy(_crossProvinceTexture); }
        }
    }
}
