using Ghost.Core;
using Ghost.Core.Graphics;
using Ghost.DSL.ShaderParser;
using Misaki.HighPerformance.Utilities;
using System.Text;

namespace Ghost.DSL.ShaderCompiler;

public struct DSLShaderError
{
    public string message;
    public int line;
    public int column;

    public override readonly string ToString()
    {
        return $"Error at {line}:{column} - {message}";
    }
}

public static class DSLShaderCompiler
{
#if GHOST_SAFETY_CHECKS
    private static PipelineState MeragePipeline(PipelineSemantic? semantic, PipelineState parent)
    {
        if (semantic == null)
        {
            return parent;
        }

        return new PipelineState
        {
            ZTest = semantic.zTest ?? parent.ZTest,
            ZWrite = semantic.zWrite ?? parent.ZWrite,
            Cull = semantic.cull ?? parent.Cull,
            Blend = semantic.blend ?? parent.Blend,
            ColorMask = semantic.colorMask ?? parent.ColorMask
        };
    }

    private static Result<string> BuildFinalShaderCode(string shaderPath, ReadOnlySpan<string> includes, string? injectedCode, string? properties)
    {
        string shaderCode;
        if (shaderPath == "hlsl_block")
        {
            if (string.IsNullOrEmpty(injectedCode))
            {
                return Result.Failure("Shader code is empty. Either provide a valid shader path or inject shader code directly.");
            }

            shaderCode = string.Empty;
        }
        else
        {
            if (!File.Exists(shaderPath))
            {
                return Result.Failure("Shader file not found: " + shaderPath);
            }

            shaderCode = File.ReadAllText(shaderPath);
        }

        var sb = new StringBuilder();
        foreach (var includePath in includes)
        {
            sb.AppendLine($"#include \"{includePath}\"");
        }

        if (!string.IsNullOrEmpty(properties))
        {
            sb.AppendLine($"#line 0 \"properties\"");
            sb.AppendLine(properties);
        }

        if (!string.IsNullOrEmpty(injectedCode))
        {
            sb.AppendLine($"#line 0 \"injected_code\"");
            sb.AppendLine(injectedCode);
        }

        if (!string.IsNullOrEmpty(shaderCode))
        {
            sb.AppendLine($"#line 0 \"{shaderPath}\"");
            sb.AppendLine(shaderCode);
        }

        return sb.ToString();
    }
#endif

    // TODO: Implement shader inheritance resolution, including property and pass merging.
    // Currently, we just ignore inheritance.
    public static Result<GraphicsShaderDescriptor> ResolveShader(DSLShaderSemantics semantics)
    {
#if GHOST_SAFETY_CHECKS
        if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var propertyInfo))
        {
            propertyInfo = default;
        }

        var passes = semantics.passes == null ? Array.Empty<PassDescriptor>() : new PassDescriptor[semantics.passes.Count];
        for (var i = 0; i < passes.Length; i++)
        {
            var pass = semantics.passes![i];
            var localPipeline = MeragePipeline(pass.localPipeline, PipelineState.Default);

            var result = BuildFinalShaderCode(pass.amplificationShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.Code);
            if (result.IsFailure)
            {
                return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
            }

            var amplificationShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.amplificationShader.entry };

            result = BuildFinalShaderCode(pass.meshShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.Code);
            if (result.IsFailure)
            {
                return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
            }

            var meshShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.meshShader.entry };

            result = BuildFinalShaderCode(pass.pixelShader.shaderPath, pass.includes.AsSpan(), pass.hlsl, propertyInfo.Code);
            if (result.IsFailure)
            {
                return Result.Failure($"Failed to build shader code for pass '{pass.name}': {result.Message}");
            }

            var pixelShaderCode = new ShaderCode { code = result.Value, entryPoint = pass.pixelShader.entry };

            passes[i] = new PassDescriptor
            {
                name = pass.name,

                amplificationShaderCode = amplificationShaderCode,
                meshShaderCode = meshShaderCode,
                pixelShaderCode = pixelShaderCode,

                localPipeline = localPipeline,
                defines = pass.defines?.ToArray() ?? Array.Empty<string>(),
                keywords = pass.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
            };
        }

        var descriptor = new GraphicsShaderDescriptor
        {
            Name = semantics.name,
            PropertyBufferSize = propertyInfo.Size,

            ShaderModel = semantics.shaderModel,
            Passes = passes
        };

        for (var i = 0; i < descriptor.Passes.Length; i++)
        {
            descriptor.Passes[i].shader = descriptor;
        }

        return descriptor;
#else
        return Result.Failure("GHOST_EDITOR is not defined");
#endif

    }

    public static Result<GraphicsShaderDescriptor> CompileGraphicsShader(Stream stream)
    {
        using var reader = new StreamReader(stream);
        return CompileGraphicsShaderCode(reader.ReadToEnd());
    }

    public static Result<GraphicsShaderDescriptor> CompileGraphicsShader(string shaderPath)
    {
        if (!File.Exists(shaderPath))
        {
            return Result.Failure("Shader file not found: " + shaderPath);
        }

        var code = File.ReadAllText(shaderPath);
        return CompileGraphicsShaderCode(code);
    }

    public static Result<GraphicsShaderDescriptor> CompileGraphicsShaderCode(string shaderCode)
    {
        try
        {
            // Use ANTLR4 parser
            var parseErrors = new List<DSLShaderError>();
            var shaderModels = AntlrShaderCompiler.ParseShaders(shaderCode, parseErrors);

            if (parseErrors.Count != 0)
            {
                var errorMessages = new StringBuilder();
                foreach (var error in parseErrors)
                {
                    errorMessages.AppendLine(error.ToString());
                }

                return Result.Failure("Failed to parse shader due to errors:\n" + errorMessages.ToString());
            }

            if (shaderModels.Count == 0)
            {
                return Result.Failure("No shader found in the provided file.");
            }

            // Convert to semantics
            var model = AntlrShaderCompiler.ConvertToSemantics(shaderModels[0], out var errors);

            if (errors.Count != 0 || model == null)
            {
                var errorMessages = new StringBuilder();
                foreach (var error in errors)
                {
                    errorMessages.AppendLine(error.ToString());
                }

                return Result.Failure("Failed to compile shader due to errors:\n" + errorMessages.ToString());
            }

            var result = ResolveShader(model);
            if (result.IsFailure)
            {
                return result;
            }

            return result.Value;
        }
        catch (Exception ex)
        {
            return Result.Failure("Failed to compile shader: " + ex.Message);
        }
    }

    public static Result<ComputeShaderDescriptor> CompileComputeShader(Stream stream)
    {
        using var reader = new StreamReader(stream);
        return CompileComputeShaderCode(reader.ReadToEnd());
    }

    public static Result<ComputeShaderDescriptor> CompileComputeShader(string shaderPath)
    {
        if (!File.Exists(shaderPath))
        {
            return Result.Failure("Shader file not found: " + shaderPath);
        }

        var code = File.ReadAllText(shaderPath);
        return CompileComputeShaderCode(code);
    }

    public static Result<ComputeShaderDescriptor> CompileComputeShaderCode(string shaderCode)
    {
        try
        {
            var parseErrors = new List<DSLShaderError>();
            var shaderModels = AntlrShaderCompiler.ParseComputeShaders(shaderCode, parseErrors);

            if (parseErrors.Count != 0)
            {
                var errorMessages = new StringBuilder();
                foreach (var error in parseErrors)
                {
                    errorMessages.AppendLine(error.ToString());
                }

                return Result.Failure("Failed to parse compute shader due to errors:\n" + errorMessages.ToString());
            }

            if (shaderModels.Count == 0)
            {
                return Result.Failure("No compute shader found in the provided file.");
            }

            var model = AntlrShaderCompiler.ConvertToComputeSemantics(shaderModels[0], out var errors);

            if (errors.Count != 0 || model == null)
            {
                var errorMessages = new StringBuilder();
                foreach (var error in errors)
                {
                    errorMessages.AppendLine(error.ToString());
                }

                return Result.Failure("Failed to compile compute shader due to errors:\n" + errorMessages.ToString());
            }

            var result = ResolveComputeShader(model);
            if (result.IsFailure)
            {
                return result;
            }

            return result.Value;
        }
        catch (Exception ex)
        {
            return Result.Failure("Failed to compile compute shader: " + ex.Message);
        }
    }

    public static Result<ComputeShaderDescriptor> ResolveComputeShader(DSLComputeShaderSemantics semantics)
    {
#if GHOST_SAFETY_CHECKS
        if (!ShaderPropertiesRegistry.TryGetInfo(semantics.name, out var propertyInfo))
        {
            propertyInfo = default;
        }

        var shaderCodes = new ShaderCode[semantics.entryPoints.Count];
        for (var i = 0; i < shaderCodes.Length; i++)
        {
            var result = BuildFinalShaderCode(semantics.entryPoints[i].shaderPath, semantics.includes.AsSpan(), semantics.hlsl, propertyInfo.Code);
            if (result.IsFailure)
            {
                return Result.Failure($"Failed to build shader code for entry point '{semantics.entryPoints[i].entry}': {result.Message}");
            }

            shaderCodes[i] = new ShaderCode { code = result.Value, entryPoint = semantics.entryPoints[i].entry };
        }

        return new ComputeShaderDescriptor
        {
            Name = semantics.name,
            PropertyBufferSize = propertyInfo.Size,
            ShaderModel = semantics.shaderModel,
            ShaderCodes = shaderCodes,
            Defines = semantics.defines?.ToArray() ?? Array.Empty<string>(),
            Keywords = semantics.keywords?.ToArray() ?? Array.Empty<KeywordsGroup>()
        };
#else
        return Result.Failure("GHOST_EDITOR is not defined");
#endif
    }
}
