<template>
  <div>
    <el-input
      type="textarea"
      :rows="2"
      placeholder="请输入内容"
      @change="sqlchange"
      :autosize="{ minRows: 8, maxRows: 16 }"
      v-model="sqlinput"
    >
    </el-input>
    <div style="margin: 20px 0"></div>
    <el-row>
      <el-col :span="24">
        <s-json-schema-editor
          :key="schemaKey"
          :schema.sync="schema"
          :show-default-value="true"
          :is-mock="true"
          @schema-change="onSchemaChange"
        >
        </s-json-schema-editor>
      </el-col>
    </el-row>
    <el-button type="primary" @click="genCode()">生成代码</el-button>
    <div style="margin: 20px 0"></div>
    <el-row>
      <vue-json-editor
        v-model="schema"
        :showBtns="false"
        :mode="'text'"
        lang="zh"
        @json-change="onJsonChange"
        @json-save="onJsonSave"
        @has-error="onError"
      />
      <br />
    </el-row>
    <div style="margin: 20px 0"></div>
    <el-dialog title="生成代码" :visible.sync="dialogVisible" width="80%">
      <!-- <el-button type="primary" @click="selectText()">复制当前页</el-button> -->
      <el-tabs v-model="activeName" :key="renderKey">
        <el-tab-pane
          v-for="(item, key) in output"
          :key="key"
          :label="key"
          :name="key"
        >
          <el-input
            :key="renderKey"
            type="textarea"
            v-model="codeOutput"
            :autosize="{ minRows: 2, maxRows: 10 }"
            placeholder="请输入内容"
          ></el-input>
          <!-- <div
            :id="key"
            class="tab-info"
            style="
              height: 50vh;
              background: #fff;
              padding: 0 20px;
              overflow-y: scroll;
            "
          /> -->
        </el-tab-pane>
      </el-tabs>
    </el-dialog>
  </div>
</template>

<script>
import vueJsonEditor from "vue-json-editor";
import { Parser } from "sql-ddl-to-json-schema";
// import { marked } from "marked";
// import hljs from "highlight.js";
// import "highlight.js/styles/atelier-plateau-light.css";

function toCamelCase(str) {
  var regExp = /[-_]\w/gi;
  return titleCase(
    str.replace(regExp, function (match) {
      return match.charAt(1).toUpperCase();
    })
  );
}

function titleCase(str) {
  const newStr = str.slice(0, 1).toUpperCase() + str.slice(1);
  return newStr;
}

function lowerCase(str) {
  const newStr = str.slice(0, 1).toLowerCase() + str.slice(1);
  return newStr;
}

function typeMap(str) {
  const kv = {
    integer: "int64",
    string: "string",
    number: "float64",
  };
  return kv[str];
}

export default {
  name: "HelloWorld",
  components: { vueJsonEditor },
  data() {
    return {
      schemaKey: "19878878",
      renderKey: 1,
      activeName: "",
      dialogVisible: false,
      schema: {
        type: "object",
        title: "title",
        properties: {
          field_1: {
            type: "string",
          },
        },
      },
      jsonformat: {},
      sqlinput: `CREATE TABLE \`gb_goods\` (
          \`goods_id\` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品id',
          PRIMARY KEY (\`goods_id\`) USING BTREE
          ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COMMENT = '商品表' ROW_FORMAT = Dynamic;`,
      output: {},
    };
  },
  created() {},
  computed: {
    codeOutput() {
      return this.output[this.activeName];
    },
  },

  mounted() {
    this.sqlchange(this.sqlinput);
    this.schemaKey = Date.now();
  },
  methods: {
    selectText() {
      const element = document.getElementById(this.activeName);
      if (document.body.createTextRange) {
        const range = document.body.createTextRange();
        range.moveToElementText(element);
        range.select();
      } else if (window.getSelection) {
        const selection = window.getSelection();
        const range = document.createRange();
        range.selectNodeContents(element);
        selection.removeAllRanges();
        selection.addRange(range);
      } else {
        alert("none");
      }
      document.execCommand("copy");
      this.$message({
        message: "复制成功",
        type: "success",
      });
    },
    onJsonChange(value) {
      this.onJsonSave(value);
    },
    onJsonSave(value) {
      this.schemaKey = Date.now();
      this.schema = Object.assign({}, value);
    },
    genCode() {
      let v = this.schema;
      this.output["model"] = this.handleModelOutput(v);
      this.output["service"] = this.handleServiceOutput(v);
      this.output["api"] = this.handleApiOutput(v);
      this.output["route"] = this.handleRouteOutput(v);
      this.output["js-api"] = this.handleJsApi(v);
      this.renderKey++;
      this.activeName = "";
      this.dialogVisible = true;
    },
    sqlchange(v) {
      // 1. 现将sql进行解析 json schema
      const parser = new Parser("mysql");
      const options = { useRef: false };
      parser.feed(v);
      const doc = parser.toJsonSchemaArray(options);
      // 2. 将json schema格式化输出
      const ob = JSON.parse(JSON.stringify(doc[0]));
      this.schema = Object.assign({}, ob);
      //   动态渲染
      this.schemaKey = Date.now();
    },
    onError(value) {
      console.log("json渲染错误了value:", value);
    },
    onSchemaChange(v) {
      this.jsonformat = v;
      this.schema = JSON.stringify(v, null, 2);
      this.output["model"] = this.handleModelOutput(v);
      this.output["service"] = this.handleServiceOutput(v);
      this.output["api"] = this.handleApiOutput(v);
      this.output["route"] = this.handleRouteOutput(v);
      this.output["js-api"] = this.handleJsApi(v);
      this.schemaKey = Date.now();
    },
    handleRouteOutput(v) {
      const tableName = toCamelCase(v.title);
      const lowTableName = v.title;
      const Output = `
	  	{
			${lowTableName} := admin.Group("${lowTableName}")
			${lowTableName}.POST("list", a.${tableName}.List${tableName})
			${lowTableName}.POST("create", a.${tableName}.Create${tableName})
			${lowTableName}.POST("detail", a.${tableName}.Get${tableName}One)
			${lowTableName}.POST("del", a.${tableName}.Delete${tableName})
		}
		`;
      return Output;
    },
    handleJsApi(v) {
      const tableName = toCamelCase(v.title);
      const lowTableName = v.title;
      const Output = `
    import request from '@/utils/request';
	export function get${tableName}(data) {
	return request({
		url: '/api/admin/${lowTableName}/detail',
		method: 'post',
		data: data
	})
	}

	export function list${tableName}(data) {
	return request({
		url: '/api/admin/${lowTableName}/list',
		method: 'post',
		data: data
	})
	}

	export function list${tableName}Export(data) {
	return request({
		url: '/api/admin/${lowTableName}/list',
		method: 'post',
		data: data,
		responseType: 'blob'
	})
	}

	export function create${tableName}(data) {
	return request({
		url: '/api/admin/${lowTableName}/create',
		method: 'post',
		data: data
	})
	}

	export function del${tableName}(data) {
	return request({
		url: '/api/admin/${lowTableName}/del',
		method: 'post',
		data: data
	})
	}
		`;
      return Output;
    },
    handleApiOutput(v) {
      const tableName = toCamelCase(v.title);
      const apiOutput = `
		package api

		import (
			"Carp/internal/app/ginplus"
			"Carp/internal/app/model"
			"Carp/internal/app/schema"
			"Carp/internal/app/service"
			"Carp/pkg/errors"
			"Carp/pkg/utils"

			"github.com/gin-gonic/gin"
			"github.com/google/wire"
			"github.com/tealeg/xlsx"
		)
		var ${tableName}ApiSet = wire.NewSet(wire.Struct(new(${tableName}Api), "*"))

		type ${tableName}Api struct {
			${tableName}Service *service.${tableName}Service
		}

		func (l *${tableName}Api) Create${tableName}(c *gin.Context) {
			req := &model.${tableName}{}
			if err := ginplus.ParseJSON(c, req); err != nil {
				ginplus.ResError(c, err)
				return
			}
			err := l.${tableName}Service.Create${tableName}(c.Request.Context(), req)
			if err != nil {
				ginplus.ResError(c, err)
				return
			}
			ginplus.ResOK(c)
		}

		func (l *${tableName}Api) Get${tableName}One(c *gin.Context) {
			req := &schema.IDResult{}
			if err := ginplus.ParseJSON(c, req); err != nil {
				ginplus.ResError(c, err)
				return
			}
			resp, err := l.${tableName}Service.Get${tableName}One(c.Request.Context(), req.ID)
			if err != nil {
				ginplus.ResError(c, err)
				return
			}
			ginplus.ResSuccess(c, resp)
		}

		func (l *${tableName}Api) List${tableName}(c *gin.Context) {
			req := &model.${tableName}ReqParams{}
			if err := ginplus.ParseJSON(c, req); err != nil {
				ginplus.ResError(c, err)
				return
			}
			resp, err := l.${tableName}Service.Get${tableName}List(c.Request.Context(), req)
			if err != nil {
				ginplus.ResError(c, err)
				return
			}
			if req.Export {
				file := resp.(*xlsx.File)
				ginplus.ResExcel(c, file, "导出信息列表")
				return
			}
			ginplus.ResSuccess(c, resp)
		}

		func (l *${tableName}Api) Delete${tableName}(c *gin.Context) {
			var req schema.Ids
			if err := ginplus.ParseJSON(c, &req); err != nil {
				ginplus.ResError(c, err)
				return
			}
			if err := l.${tableName}Service.Del${tableName}(c.Request.Context(), req.IDs...); err != nil {
				ginplus.ResError(c, err)
				return
			}
			ginplus.ResOK(c)
		}
	  `;
      return apiOutput;
    },
    handleServiceOutput(v) {
      const properties = v.properties;
      const tableName = toCamelCase(v.title);
      var primary = ``;
      Object.keys(properties).forEach(function (key) {
        const o = properties[key];
        const skey = toCamelCase(key);
        if (Object.prototype.hasOwnProperty.call(o, "$comment")) {
          primary = skey;
        }
      });
      var primaryLower = lowerCase(primary);
      const serviceOutput = `
	package service
	import (
		"Carp/internal/app/model"
		"Carp/internal/app/schema"
		"Carp/pkg/errors"
		"Carp/pkg/utils/excelize"
		"context"
		"encoding/json"

		"github.com/google/wire"
	) 
	var ${tableName}ServiceSet = wire.NewSet(wire.Struct(new(${tableName}Service), "*"))
	
	type ${tableName}Service struct {
		${tableName}    *model.${tableName}Repo
	}
	
	func (a *${tableName}Service) Get${tableName}List(ctx context.Context, req *model.${tableName}ReqParams) (interface{}, error) { 
      var l []*model.${tableName}
        cnt, err := a.${tableName}.Q().Filter(req).List(ctx, &l)
        if err != nil {
          return nil, errors.WithStack(err)
        }
      
        if req.Export {
          rst, _ := excelize.Excelize(l,
            excelize.ColumnFilter("create_time", excelize.FilterTimestamp),
          )
          return rst, nil
        }
        pagination := &schema.PaginationResult{
          Count:    cnt,
          List:     l,
          PageNum:  req.PageNum,
          PageSize: req.PageSize,
        }
        return pagination, nil
      }

	  func (a *${tableName}Service) Get${tableName}One(ctx context.Context, ${primaryLower} int64) (interface{}, error) {
		var o model.${tableName}
		err := a.${tableName}.Q().${primary}(${primaryLower}).One(ctx, &o)
		if err != nil {
			return nil, err
		}
		return o, nil
		}

	  func (a *${tableName}Service) Del${tableName}(ctx context.Context, ${primaryLower} ...int64) error {
	  var l []model.${tableName}
	  _, err := a.${tableName}.Q().${primary}In(${primaryLower}...).List(ctx, &l)
	  if err != nil {
	  	return err
	  }
	  for _, d := range l {
	  	a.${tableName}.Delete(ctx, d.${primary})
	  }
	  return nil
	  }

	func (a *${tableName}Service) Create${tableName}(ctx context.Context, r *model.${tableName}) error {
	if r.${primary} == 0 {
		_, err := a.${tableName}.Create(ctx, r)
		if err != nil {
			return err
		}
		return nil
	}
	return a.${tableName}.Updates(ctx, r, a.${tableName}.Q().${primary}(r.${primary}))
	}

		`;
      return serviceOutput;
    },
    getMyStruct() {
      var oReq = new XMLHttpRequest();
      oReq.open("POST", "/sql", false); // 同步请求
      oReq.setRequestHeader("Content-type", "application/json");
      console.log(this.sqlinput);
      oReq.send(JSON.stringify({ content: this.sqlinput })); //发送数据需要自定义，这里发送的是JSON结构
      var result = oReq.responseText; //响应结果
      return result;
    },
    handleModelOutput(v) {
      var struct = this.getMyStruct();
      console.log("struct", struct);
      const requires = v.required;
      const properties = v.properties;
      const tableName = toCamelCase(v.title);
      const tableNameLower = lowerCase(tableName);
      var primary = ``;
      Object.keys(properties).forEach(function (key) {
        const o = properties[key];
        const skey = toCamelCase(key);
        if (Object.prototype.hasOwnProperty.call(o, "$comment")) {
          primary = skey;
        }
      });
      var primaryLower = lowerCase(primary);
      var enumUpdate = ``;
      var enumdefine = ``;
      var paramsheader = `
    var ${tableName}Set = wire.NewSet(wire.Struct(new(${tableName}Repo), "*"))

    type ${tableName}Repo struct {
      DB    *gorm.DB
      Cache cache.Cache
    }

    // GetTableName get sql table name.获取数据库名字
    func (obj *${tableName}Repo) TableName() string {
      return "${v.title}"
    }

    func (obj *${tableName}Repo) PreTableName(s string) string {
      b := strings.Builder{}
      b.WriteString(obj.TableName())
      b.WriteString(".")
      b.WriteString(s)
      return b.String()
    }

    // Delete By ID
    func (obj *${tableName}Repo) Delete(ctx context.Context, ${primaryLower} int64) error {
      err := obj.DB.WithContext(ctx).Delete(&${tableName}{}, ${primaryLower}).Error
      return err
    }

    // CacheKey${primary} CacheKey generate by ids
    func (obj *${tableName}Repo) CacheKey${primary}(${primaryLower} int64) string {
      return strings.Join([]string{obj.TableName(), "${primaryLower}", utils.AsString(${primaryLower})}, "_")
    }

    // Updates 更新
    func (obj *${tableName}Repo) Updates(ctx context.Context, input *${tableName}, q *${tableNameLower}Q) error {
      Q := q.Query(ctx)
      key := ""
      if k, _ := Q.InstanceGet("cache_key"); k != nil {
        key = utils.AsString(k)
      }
      return obj.Cache.Exec(ctx, key, func(ctx context.Context) error {
        return Q.Updates(*input).Error
      })
    }

    // Create 创建
    func (obj *${tableName}Repo) Create(ctx context.Context, input *${tableName}) (*${tableName}, error) {
      if err := obj.DB.WithContext(ctx).Create(input).Error; err != nil {
        return nil, err
      }
      return input, nil
    }

    type ${tableNameLower}Q struct {
	  BaseModelFunc
      Repo  *${tableName}Repo
      opts []GormOptionFunc
    }

    func (obj *${tableName}Repo) Q() *${tableNameLower}Q {
		q := &${tableNameLower}Q{
			Repo: obj,
		}
		q.BaseModelFunc = obj.PreTableName
		return q
    }

    // List 查询列表
    func (obj *${tableNameLower}Q) List(ctx context.Context, value interface{}) (int64, error) {
      var cnt int64
      err := obj.Query(ctx).Order("${primaryLower} desc").Find(value).Offset(-1).Count(&cnt).Error
      return cnt, err
    }

    //One 查询单个
    func (obj *${tableNameLower}Q) One(ctx context.Context, value interface{}) error {
      Q := obj.Query(ctx)
      cache_key := ""
      // 缓存只适用于单行全列的情况
      if k, _ := Q.InstanceGet("cache_key"); len(obj.opts) == 1 && k != nil {
        cache_key = utils.AsString(k)
        Q = Q.Select("*")
      }
      return obj.Repo.Cache.Query(ctx, cache_key, value, func(ctx context.Context) error {
        err := Q.First(value).Error
        if errors.Is(err, gorm.ErrRecordNotFound) {
          return errors.ErrIdCanNotFound
        }
        return err
      })
    }

    func (obj *${tableNameLower}Q) Query(ctx context.Context) *gorm.DB {
      db := obj.Repo.DB.WithContext(ctx).Model(&${tableName}{})
      for _, f := range obj.opts {
        db = f(db)
      }
      return db
    }

	func (obj *${tableNameLower}Q) where(key string, value interface{}) {
		obj.opts = append(obj.opts, func(db *gorm.DB) *gorm.DB {
			db = db.Where(obj.Repo.PreTableName(key), value)
			return db
		})
	}

	func (p *${tableName}ReqParams) GetPageNum() int     { return p.PageNum }
	func (p *${tableName}ReqParams) GetPageSize() int    { return p.PageSize }
	func (p *${tableName}ReqParams) GetFields() []string { return p.Fields }
	func (p *${tableName}ReqParams) GetExport() bool     { return p.Export }

      type ${tableName}ReqParams struct {
        Query    *${tableName}Params \`json:"query,omitempty"\`
        Export   bool           \`json:"export,omitempty"\`
        Fields   []string       \`json:"fields,omitempty"\`
        PageNum  int            \`json:"page_num,omitempty"\`
        PageSize int            \`json:"page_size,omitempty"\`
      }
      type ${tableName}Params struct {\n`;
      var center = ``;
      var paramstail = `}`;
      var optCommon = ``;

      var filterHeader = `
      func (opt *${tableNameLower}Q) Filter(para *${tableName}ReqParams) *${tableNameLower}Q {
        	if para != nil {
				if para.Export {
					para.PageNum = 1
					para.PageSize = 10000
				}
				opt.opts = append(opt.opts, opt.Select(para.Fields...))
				opt.opts = append(opt.opts, opt.Pagination(para))	
		        if para.Query != nil {
      `;
      var filtercenter = ``;
      var filtertail = `}}
      return opt}`;

      Object.keys(properties).forEach(function (key) {
        const o = properties[key];
        const skey = toCamelCase(key);
        const tableKey = `${tableName}` + toCamelCase(key);
        const type = typeMap(o.type);
        // 当枚举类型的时候做处理
        if (
          Object.prototype.hasOwnProperty.call(o, "enum") &&
          o.enum.length > 0
        ) {
          const enumlist = o.enum;
          const enumdesc = o.enumDesc;
          var enumbegin = `
          type ${tableKey} ${type} 
            const (
          `;
          var enumcenter = ``;
          var enumend = `)`;
          //   生成枚举相关代码
          enumdesc.split("\n").forEach(function (value, index) {
            const v = enumlist[index];
            var s = `
              ${tableKey}${value} ${tableKey} = ${v} \n
            `;
            var f = `
              func (g ${tableKey}) ${value}() bool { return g == ${tableKey}${value} } \n
              func (obj *${tableName}Repo) ${value}(ctx context.Context, id int64) error {
              	return obj.Updates(ctx, &${tableName}{${skey}: ${tableKey}${value}}, obj.Q().${primary}(id))
              } \n
            `;
            enumcenter += s;
            enumUpdate += f;
          });
          enumdefine += enumbegin + enumcenter + enumend;
        }

        if (key.indexOf("_time") > -1) {
          center += `${skey}Interval []interface{} \`json:"${key}|interval,omitempty"\`\n`;
          filtercenter += `	
          if len(para.Query.${skey}Interval) > 0 {
				    opt.${skey}Interval(para.Query.${skey}Interval)
			    }
          `;
          optCommon += `
            func (obj *${tableNameLower}Q) ${skey}Interval(interval []interface{}) *${tableNameLower}Q {
              fn := func(db *gorm.DB) *gorm.DB {
                	db = db.Clauses(
                      TimeStampDate{
                        Column: "${key}",
                        Values: interval,
                      })
		            return db
	            }
              obj.opts = append(obj.opts, fn)
              return obj
            }
            \n
          `;
        }

        // id过滤掉，不能生成，List里面可以查询ID会有风险
        if (key.indexOf("_id") > -1) {
          center += `${skey}In []${type} \`json:"${key}|in,omitempty"\`\n`;
          filtercenter += `	
          if len(para.Query.${skey}In) > 0 {
				    opt.${skey}In(para.Query.${skey}In...)
			    }
          `;
          optCommon += `
            func (obj *${tableNameLower}Q) ${skey}In(${skey}s ...${type}) *${tableNameLower}Q {
		      obj.where("${key} in (?)", ${skey}s)
              return obj
            }
            \n
          `;
        }

        if (requires.indexOf(key) > -1) {
          center += `${skey} ${type} \`json:"${key},omitempty"\`\n`;
          if (type == "string") {
            filtercenter += `	
            if para.Query.${skey} != "" {
              opt.${skey}(para.Query.${skey})
            }
            `;
          } else if (key !== "id") {
            // id过滤掉，不能生成，List里面可以查询ID会有风险
            filtercenter += `	
            if para.Query.${skey} != 0 {
              opt.${skey}(para.Query.${skey})
            }
            `;
          }
          optCommon += `
            func (obj *${tableNameLower}Q) ${skey}(${skey} ${type}) *${tableNameLower}Q {
              obj.where("${key} = ?", ${skey})
              return obj
            }
            \n
          `;
          if (type == "string") {
            center += `${skey}Like ${type} \`json:"${key}|like,omitempty"\`\n`;
            filtercenter += `	
            if para.Query.${skey}Like != "" {
              opt.${skey}Like(para.Query.${skey}Like)
            }
            `;
            optCommon += `
            func (obj *${tableNameLower}Q) ${skey}Like(${skey} ${type}) *${tableNameLower}Q {
              obj.where("${key} like ?", "%"+${skey}+"%")
              return obj
            }
            \n
          `;
          }
        }
      });
      const modelOutput =
        struct +
        enumdefine +
        enumUpdate +
        paramsheader +
        center +
        paramstail +
        "\n" +
        optCommon +
        filterHeader +
        filtercenter +
        filtertail;
      return modelOutput;
    },
  },
};
</script>

